Say I have an object that has some attributes with default values. I set up the default values for these attributes at a model level like so...
after_initialize :set_defaults
def set_defaults
self.name ||= "Player"
self.level ||= 5
end
I want to have a way for the user to set what the default values are though. The workflow would be the user would have an action to set their own defaults (in the view, this would be done via a form) and then these defaults would be used in the model method set_defaults
, like so...
def set_defaults
self.name ||= user_default_for_name
self.level ||= user_default_for_level
end
I have thought about approaching this by making a "default" object in the code that the user "edits". Then, in the model method, the defaults will be set from the values in that object. But is there any better way of doing this?
In general I'm not a fan of callbacks to set default values for attributes. This is a very dated approach and can have wierd and unexpected side effects due to the timing issues.
If the default values are not part of the database schema (like booleans that default to false for example) but can be defined on the class level you can use the ActiveModel or ActiveRecord attributes API to set defaults.
attribute :level, :integer, default: 5
This is also an area where you could consider using the Form Object Pattern to avoid making your model into even more of god class.
Form Objects are classes that wrap a model for the purpose of handling user input.
class PlayerForm
include ActiveModel::Model
include ActiveModel::Attributes
attr_accessor :player
attribute :level, :integer, default: 5
attribute :name, :string, default: "Player"
# this lets the form object interact with Rails API's
# for routing
delegate :to_model, :model_name, to: :player
def initialize(**attributes)
super
@player = Player.new
end
def save
return false unless self.valid?
@player.assign_attributes(level: level, name: name)
@player.save
end
end
On the controller level we just swap out the Player class for the PlayerForm class:
class PlayerController < ApplicationController
def new
@player = PlayerForm.new
end
def create
@player = PlayerForm.new(player_attributes)
if @player.save
redirect_to @player
else
render :new
end
end
end
While the immediate gains here can seem small now setting the defaults only happens when you actually want it - when populating a form and not every time the model is instanciated.
I have thought about approaching this by making a "default" object in the code that the user "edits". Then, in the model method, the defaults will be set from the values in that object. But is there any better way of doing this?
If you want the default values to be personizable by the users via the GUI you would create a model (and the related routes and controller) for that:
# rails g model UserDefault name:string level:integer
class UserDefault < ApplicationRecord
belongs_to :user
end
You could then plug the user defaults model into the Form Object we created earlier:
class PlayerForm
include ActiveModel::Attributes
include ActiveModel::Model
attr_accessor :player
attribute :level, :integer
attribute :name, :string
# this lets the form object interact with Rails API's
# for routing
delegate :to_model, :model_name, to: :player
def initalize(**attributes)
super
@player = Player.new
end
def save
return false unless self.valid?
@player.assign_attributes(level: level, name: name)
@player.save
end
def assign_user_defaults(defaults)
assign_attributes(defaults.attributes.slice(:name, :level))
end
end
class PlayerController < ApplicationController
before_action :authenticate_user
def new
@player = PlayerForm.new
@player.assign_user_defaults(current_user.user_default) if current_user.user_default
end
def create
@player = PlayerForm.new(player_attributes)
if @player.save
redirect_to @player
else
render :new
end
end
end
Here I'm making the assumption that there is an authentication system and some sort of method on the user that returns their preferred set of defaults.