Domanda

I've a record/model that's filled up with a lot of boolean flags, short strings, misc settings, etc, and I want to refactor them into an encapsulated class.

I would normally create an inner class for the parent to 'own', but what's the best practice in Rails?

  • Just leaving everything in the record? composed_of? Not very clean though.
  • Serializing? Becomes not easily searchable + overhead + extensibility issues. Still not clean.
  • Create another record, associate it, and delegate? But have to delegate and manage assignment, aliases (boolean? fields), create/save callbacks, more stuff I'm unaware of...
  • Is there a way to create in 'inner record'?
  • Another way?

An example would be having a table Client, with a low number of rows 10~20. Each Client is has long list of options, currently stored under Client columns. So there are fields such as:

c = Client.find(1)
c.theme_color # "blue"
c.session_timeout_seconds # 1800
c.branding_logo # "client_a.png"
c.require_logout_confirmation # true

In a pure OOP system without rails, I would refactor these "instance variables" into a nested Client::Options class. Then all the options become organized inside a single nested class and are encapsulated inside Client. This way, nobody else needs to know that Client::Options and Client are closely related (they would still just message c.theme_color without knowing that Client will delegate the call to Client::Options within), and this would also become a loosely-coupled refactoring since no other classes or method calls need to change.

However, the nested class thing isn't possible (as far as I know) in rails, so I'm looking for a 'best practices' solution.

È stato utile?

Soluzione

This is the solution I ended up going with:

class Client < ActiveRecord::Base
  has_one :option_set, :dependent => :destroy, :autosave => true, :validate => true, :inverse_of => :client
  before_create :build_option_set

  # accesses all these fields through OptionSet
  # :allow_nil is required or else delegation will fail - it appears delegation occurs before :build_option_set
  delegate_option_set_args = [
    :option_a,
    :option_b,
    :option_c,
    # etc
  ].map { |f| [f, :"#{f}=", :"#{f}?" ] }.flatten << { :to => :option_set, :allow_nil => true }

  delegate(*delegate_option_set_args)
end


class OptionSet < ActiveRecord::Base
  validates_presence_of :client_id
  validates_inclusion_of :option_c, :in => [ "foo", "bar", "buzz", "fizz" ]
  belongs_to :client, :inverse_of => :option_set
end

Now, although OptionSet is still accessible from "outside" of Client, it still effectively encapsulates all these fields into their own class. Accessing these fields should be done via messaging the Client, e.g. Client.first.option_a, rather than modifying the OptionSet. Additional work can be done to enforce that OptionSet is not modified directly.

I've not found a way to instantiate the option fields at the same time as Client creation, though:

client = Client.create!(client_args)
client.update_arguments!(
  :option_a => 1,
  :option_b => false,
  :option_c => "foo"
)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top