-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Create new serialization logic and use to generate JSON for forwarding.
Future work: - improve test coverage (on presenters themselves) - Look into API versioning, use this representation in HTTP API too in a new version - Delete old formatting code once released. - Sort out the mess of different reading representations we use in different places and radically simplify that part of everything
- Loading branch information
1 parent
9849cc2
commit d0245d6
Showing
11 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module PresentationHelper | ||
def present(model, options={}) | ||
Presenters.present(model, current_user, self, options) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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, | ||
User => Presenters::UserPresenter, | ||
Component => Presenters::ComponentPresenter, | ||
Sensor => Presenters::SensorPresenter, | ||
Measurement => Presenters::MeasurementPresenter, | ||
} | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +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.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.exposed_fields.include?(method) | ||
model.public_send(method, *args, &block) | ||
else | ||
super | ||
end | ||
end | ||
|
||
def present(other_model, options={}) | ||
Presenters.present(other_model, current_user, render_context, options) | ||
end | ||
|
||
private | ||
|
||
attr_reader :model, :current_user, :options, :render_context | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
module Presenters | ||
class DevicePresenter < BasePresenter | ||
alias_method :device, :model | ||
|
||
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 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 | ||
{ | ||
stopped_publishing: device.notify_stopped_publishing, | ||
low_battery: device.notify_low_battery | ||
} | ||
end | ||
|
||
def location | ||
if options[:with_location] | ||
{ | ||
exposure: device.exposure, | ||
elevation: device.elevation.try(:to_i) , | ||
latitude: device.latitude, | ||
longitude: device.longitude, | ||
geohash: device.geohash, | ||
city: device.city, | ||
country_code: device.country_code, | ||
country: device.country_name | ||
} | ||
end | ||
end | ||
|
||
def data_policy | ||
{ | ||
is_private: authorized? ? device.is_private : "[FILTERED]", | ||
enable_forwarding: authorized? ? device.enable_forwarding : "[FILTERED]", | ||
precise_location: authorized? ? device.precise_location : "[FILTERED]" | ||
} | ||
end | ||
|
||
def hardware | ||
{ | ||
name: device.hardware_name, | ||
type: device.hardware_type, | ||
version: device.hardware_version, | ||
slug: device.hardware_slug, | ||
last_status_message: authorized? ? device.hardware_info : "[FILTERED]", | ||
} | ||
end | ||
|
||
def owner | ||
if options[:with_owner] && device.owner | ||
present(device.owner, with_devices: false) | ||
end | ||
end | ||
|
||
def postprocessing | ||
device.postprocessing if options[:with_postprocessing] | ||
end | ||
|
||
def device_token | ||
authorized? ? device.device_token : "[FILTERED]" | ||
end | ||
|
||
def mac_address | ||
authorized? ? device.mac_address : "[FILTERED]" | ||
end | ||
|
||
def components | ||
present(device.components) | ||
end | ||
|
||
private | ||
|
||
def authorized? | ||
!options[:never_authorized] && policy.show_private_info? | ||
end | ||
|
||
def policy | ||
@policy ||= DevicePolicy.new(current_user, device) | ||
end | ||
|
||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,4 +24,7 @@ def update_password? | |
update? | ||
end | ||
|
||
def show_private_info? | ||
update? | ||
end | ||
end |