Project

model_view

0.0
No release in over 3 years
Low commit activity in last 3 years
Composable serialisation for models
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 2.1.4
~> 0.10
~> 5.1.4
~> 12.3.3
~> 3.0
~> 1.9
 Project Readme

model_view

Composable serialisation for models

ModelView let's you define views for your models in one place.

Why ModelView?

At OfferZen, most of our rails models can be presented in al least two ways. For example, an Interview Request is presented differently to a candidate than to a company. But, there are also a lot of fields that get presented the same to both candidates and companies. We found ourselves duplicating a lot of our serialisation code in our controllers and the code started to get out of hand.

ModelView works with Rails, but Rails is definitely not a requirement

How to use ModelView

  1. Include the gem in your gemfile
gem "model_view", '~> 0.1'
  1. Create a view class (we put these in app/model_views)
class PersonView
  extend ModelView

end
  1. Define fields
field :name

field(:first_name) { |person| person.name.split(' ').first }

field :is_current_user, {context: current_user} do |person, current_user|
  person == current_user
end

field :type, constant: 'User'

field :person_name, alias_for: :name
  1. Let ModelView serialise an instance
person = Person.find(1)
PersonView.as_hash(person, context: {current_user: current_user})
=> {
  name: "Billy Bob",
  first_name: "Billy",
  is_current_user: false
}

🐒-patching the model

ModelView can add a convenience method to the model class

Example

class PersonModelView
  model Person
  field :id
end

p = Person.find 1

p.as_hash(context: {current_user: current_user})
=> {
  id: 1
}

When using ModelView in Rails, remember to add an initializer that requires your model views.

Example initializer:

# require.rb
Dir["#{Rails.root}/app/model_views/*.rb"].each do |file|
  require File.basename(file, File.extname(file))
end

Scopes

Scopes allows you to create serialisation snippets that can be composed

Example:

field :id

scope :demographics do
  field :name
  field(:first_name) { |person| person.name.split(' ').first }
  field(:last_name) { |person| person.name.split(' ').last }
end

scope :status do
  field :is_current_user, context[:current_user] do |person, current_user|
    person == current_user
  end
end

scope :all do
  extend_scope :demographics
  extend_scope :status
end
person = Person.find(1)

PersonView.as_hash(person, {context: {current_user: current_user}})
=> {
  id: 1
}


PersonView.as_hash(person, {context: {current_user: current_user}, scope: :demographics})
=> {
  id:   1,
  name: "Billy Bob",
  first_name: "Billy",
  last_name: "Bob",
}

PersonView.as_hash(person, {context: {current_user: current_user}, scope: :all})
=> {
  id:   1,
  name: "Billy Bob",
  first_name: "Billy",
  last_name: "Bob",
  is_current_user: true
}

Updating models

ModelView can also be used to update models. This is achieved by creating setters.

The easiest way to create a setter is to set the setter flag to true when defining a field:

field :name, setter: true

Setters can also be defined outside of the field macro:

setter :name

Setters defined in this way are naive (obj.send("#{field}=", value)) and will not automatically call the save method on the model. See below on how to create an after_update hook.

Finally, for more complex updaters, a block can be passed:

setter(:name) do |obj, name|
  first_name, last_name = name.split(" ")
  obj.first_name = first_name
  obj.last_name = last_name
  obj.save!
end

To avoid having to explicitly save the model in every block, one can define an after_update block:

after_update { |obj| obj.save! }

field :telephone_number, setter: true
setter(:name) do |obj, name|
  first_name, last_name = name.split(" ")
  obj.first_name = first_name
  obj.last_name = last_name
end

Models can then be updated using ModelView.update:

PersonView.update(person, {phone: "+123 456 7890"}, scope: :contact_details)