I've used ActiveModelSerializers (AMS) for a long time. It's development history is unique as there are 3 different major branches of it with breaking changes per branch. I don't recommend that you use AMS anymore as there isn't a ton of development happening around it. I'm mostly writing this tutorial because I keep re-explaining this solution to work folk.
In this tutorial, I'll show you how to add direct or low-level control over you attributes as a way to present every method on your object be available as attributes.
This is a good starting point, but this is what we want to serialize on.
class Product
attr_accessor :data
def new(data)
@data = data
end
def presenting_method
@data[:some_key]
end
def read_attribute_for_serialization(n)
@data[n]
end
end
In this case, we want to serialize present_method
as well as a majority of all the other methods within the Product
class.
We have a couple different ways of doing it with different nuances per method.
This one is easy and works great if you want to proxy < 15 or so methods. This one is also important if you want to make sure you don't overexpose methods from your class that you want to serialize.
class ProductSerializer < ActiveModel::Serializer
attributes :presenting_method
def presenting_method
object.presenting_method
end
end
Delegators or endless functions make writing the above more effective, but typically delegators are the preferred method.
In this example, I'll use Ruby On Rails delegate
class for delegation. If you aren't depending on Ruby on Rails then you can use these two methods from the Ruby stdlib
: Forwardable or Delegator. If you are interested in these methods please checkout this blogpost by AppSignal on the topic.
With endless functions:
class ProductSerializer < ActiveModel::Serializer
attributes :presenting_method
def presenting_model = object.presending_method
end
Now with delegators:
class ProductSerializer < ActiveModel::Serializer
attributes :presenting_method
delegate :presenting_method, to: :object
# if you had more than one method you want to proxy or delegate to an object you
# can do so by specifying multiple methods:
#
# delegate :presenting_method, :another_method, :meth1, :meth2, to: :object
end
In this last example, I'll show you how to do the same thing via meta-programming. Note: this is dangrous as it'll likely expose more than you are interested in.
The trick to making this work is available within the README.md for AMS 0.8. This will be the basis of what we'll be working with for this example.
class ProductSerializer < ActiveModel::Serializer
attributes :first_name, :last_name
def attributes
hash = super
if current_user.admin?
hash["ssn"] = object.ssn
hash["secret"] = object.mothers_maiden_name
end
hash
end
end
In this case, we'll just need to build out a hash of serialized attribute => value
.
The first step will be getting the respectful methods we want to convert to serialized attributes through meta-programming:
> Product.new(test: 'hello')
.public_methods(false)
.excluding(:data, :data=, :read_attribute_for_serialization)
# [:presenting_model]
Using the above example through the REPL now we can finish the example by using our meta-programming above within the serializer:
class ProductSerializer < ActiveModel::Serializer
def attributes
hash = super
object
.public_methods(false)
.excluding(:data, :data=, :read_attribute_for_serialization)
.each do |attribute|
hash[attribute] = object.send(attribute)
end
hash
end
end