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"
)