Skip to content

Commit

Permalink
further work on new presentation logic
Browse files Browse the repository at this point in the history
  • Loading branch information
timcowlishaw committed Jul 10, 2024
1 parent 696560b commit 762bdf9
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 58 deletions.
23 changes: 20 additions & 3 deletions app/lib/presenters.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
module Presenters
# This is work in progress we're releasing early so
# that it can be used in forwarding to send the current
# values as they're received.
# TODO: add presenter tests, finish refactor following
# spec in your spreadsheet, remove unneeded options,
# use in appropriate views, add unauthorized_fields logic
# delete unneeded code in models and views.
PRESENTERS = {
Device => Presenters::DevicePresenter
Device => Presenters::DevicePresenter,
User => Presenters::UserPresenter,
Component => Presenters::ComponentPresenter,
Sensor => Presenters::SensorPresenter,
Measurement => Presenters::MeasurementPresenter,
}

def self.present(model, user, render_context, options={})
PRESENTERS[model.class]&.new(model, user, render_context, options).as_json
def self.present(model_or_collection, user, render_context, options={})
if model_or_collection.is_a?(Enumerable)
model_or_collection.map { |model| present(model, user, render_context, options) }
else
PRESENTERS[model_or_collection.class]&.new(
model_or_collection, user, render_context, options
).as_json
end
end
end
32 changes: 24 additions & 8 deletions app/lib/presenters/base_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
module Presenters
class BasePresenter

def default_options
{}
end

def exposed_fields
[]
end

def initialize(model, current_user=nil, render_context=nil, options={})
@model = model
@current_user = current_user
@render_context = render_context
@options = self.default_options.merge(options)
end

def as_json(_opts=nil)
self.class.exposed_fields.inject({}) { |h, m|
h.merge(m => self.send(m))
self.exposed_fields.inject({}) { |hash, field|
value = self.send(field)
value.nil? ? hash : hash.merge(field => value)
}
end

def method_missing(method, *args, &block)
if self.class.exposed_fields.include?(method)
device.public_send(method, *args, &block)
if self.exposed_fields.include?(method)
model.public_send(method, *args, &block)
else
super
end
end


def present(model, options={})
Presenters.present(model, current_user, render_context, options)
def present(other_model, options={})
Presenters.present(other_model, current_user, render_context, options)
end

private

attr_reader :current_user, :options, :render_context
attr_reader :model, :current_user, :options, :render_context
end
end
50 changes: 50 additions & 0 deletions app/lib/presenters/component_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Presenters
class ComponentPresenter < BasePresenter

alias_method :component, :model

def default_options
{ readings: nil }
end

def exposed_fields
%i{key sensor last_reading_at latest_value previous_value readings}
end

def sensor
present(component.sensor)
end

def latest_value
data = component.device.data
data[component.sensor_id.to_s] if data
end

def previous_value
old_data = component.device.old_data
old_data[component.sensor_id.to_s] if old_data
end

def readings
readings = options[:readings]
if readings
readings.flat_map { |reading| format_reading(reading) }.compact
end
end

private

def format_reading(reading)
# TODO sort out the mess of multiple reading formats used ini
# DataParser, RawStorer, etc, etc.
reading.data.map { |entry|
timestamp = entry.timestamp
value = entry.sensors&.find { |sensor|
sensor["id"] == component.sensor_id
}.dig("value")
{ timestamp: timestamp, value: value } if value
}.compact

end
end
end
65 changes: 18 additions & 47 deletions app/lib/presenters/device_presenter.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
module Presenters
class DevicePresenter < BasePresenter
alias_method :device, :model

DEFAULT_OPTIONS = {
with_owner: true,
with_data: true,
with_postprocessing: true,
with_location: true,
slim_owner: false,
never_authorized: false,
data: nil
}

def initialize(device, current_user=nil, render_context=nil, options={})
@device = device
@current_user = current_user
@render_context = render_context

@options = DEFAULT_OPTIONS.merge(options)
end

def self.exposed_fields
%i{id uuid name description state system_tags user_tags last_reading_at created_at updated_at notify device_token mac_address postprocessing location data_policy hardware owner data}
end

def self.compactable_fields
%i{device_token}
def default_options
{
with_owner: true,
with_data: true,
with_postprocessing: true,
with_location: true,
slim_owner: false,
never_authorized: false,
readings: nil
}
end

def data
# TODO allow for passing in fresh data
options[:data] || device.formatted_data if options[:with_data]
def exposed_fields
%i{id uuid name description state system_tags user_tags last_reading_at created_at updated_at notify device_token mac_address postprocessing location data_policy hardware owner components}
end

def notify
Expand Down Expand Up @@ -73,24 +59,8 @@ def hardware
end

def owner
# TODO refactor this into its own presenter
if options[:with_owner] && device.owner
owner = device.owner
owner_data = {
id: owner.id,
user_id: owner.id,
username: owner.username,
url: owner.url,
}
if options[:slim_owner]
owner_data
else
owner_data.merge({
profile_picture: render_context&.profile_picture_url(owner),
location: owner.location,
device_ids: owner.cached_device_ids
})
end
present(device.owner, with_devices: false)
end
end

Expand All @@ -103,13 +73,14 @@ def device_token
end

def mac_address
# TODO deal with all this "FILTERED nonsense"
authorized? ? device.mac_address : "[FILTERED]"
end

private
def components
present(device.components)
end

attr_reader :device
private

def authorized?
!options[:never_authorized] && policy.show_private_info?
Expand Down
10 changes: 10 additions & 0 deletions app/lib/presenters/measurement_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module Presenters
class MeasurementPresenter < BasePresenter

alias_method :measurement, :model

def exposed_fields
%i{id name description unit uuid definition}
end
end
end
14 changes: 14 additions & 0 deletions app/lib/presenters/sensor_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module Presenters
class SensorPresenter < BasePresenter

alias_method :sensor, :model

def exposed_fields
%i{id parent_id name description unit created_at updated_at uuid default_key datasheet unit_definition measurement tags}
end

def measurement
present(sensor.measurement)
end
end
end
42 changes: 42 additions & 0 deletions app/lib/presenters/user_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
module Presenters
class UserPresenter < BasePresenter

alias_method :user, :model

def default_options
{
with_devices: true
}
end

def exposed_fields
%i{id uuid role username profile_picture url location email legacy_api_key devices created_at updated_at}
end

def profile_picture
render_context&.profile_picture_url(user)
end

def email
user.email if authorized?
end

def legacy_api_key
user.legacy_api_key if authorized?
end

def devices
present(user.devices) if options[:with_devices]
end

private

def authorized?
policy.show_private_info?
end

def policy
@policy ||= UserPolicy.new(current_user, user)
end
end
end

0 comments on commit 762bdf9

Please sign in to comment.