As of I think Rails 5 or 6, you can use ActiveModel::Model
to make active-record-like objects and I think this obviates the need for presenters/decorates, etc.
Suppose a User
has one or more ContactPreference
instances, each with a type like push notifications, email, text. Suppose you want to make a class to simplify this:
class UserContactPreferences
include ActiveModel::Model
attr_accessor :user, :sms, :email, :push
end
This class now behaves pretty much an Active Record, so you can do stuff like
<%= form_with model: user_contact_preferences do %>
and the like. Note that you may need to implement some methods depending on your routes, e.g. create vs. update:
class UserContactPreferences
include ActiveModel::Model
attr_accessor :user, :sms, :email, :push
def to_param = user.to_param # used to create routes with ids in them
def persisted? = true # if false, Rails will generate different routes
# since it views this is a new record
end
How you create instances depends, but the simplest way is:
user_contact_preferences = UserContactPreferences.new(
user: user,
sms: user.contact_preferences.detect { |cp| cp.type == "sms" },
email: user.contact_preferences.detect { |cp| cp.type == "email" },
push: user.contact_preferences.detect { |cp| cp.type == "push" },
)
The use of attr_accessor
provides that constructor.
If you are using a service layer, some service could do this, or you could put a self.
method in the class itself:
class UserContactPreferences
def self.from_user(user)
# ...
end
# ...
end
This may seem a bit manual, but it has the virtue of using Rails stuff directly and not introducing a gem that may or may not get updated and may or may not work with new versions of Rails.
Also, to_json
should just work.
[ Edit to add not on JSON + fix typo ]