I find myself using hash arguments to constructors quite a bit, especially when writing DSLs for configuration or other bits of API that the end user will be exposed to. What I end up doing is something like the following:
class Example
PROPERTIES = [:name, :age]
PROPERTIES.each { |p| attr_reader p }
def initialize(args)
PROPERTIES.each do |p|
self.instance_variable_set "@#{p}", args[p] if not args[p].nil?
end
end
end
Is there no more idiomatic way to achieve this? The throw-away constant and the symbol to string conversion seem particularly egregious.
Given your hashes would include
ActiveSupport::CoreExtensions::Hash::Slice
, there is a very nice solution:I would abstract this to a generic module which you could include and which defines a "has_properties" method to set the properties and do the proper initialization (this is untested, take it as pseudo code):
There are some useful things in Ruby for doing this kind of thing. The OpenStruct class will make the values of a has passed to its initialize method available as attributes on the class.
The docs are here: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/ostruct/rdoc/OpenStruct.html
What if you don't want to inherit from OpenStruct (or can't, because you're already inheriting from something else)? You could delegate all method calls to an OpenStruct instance with Forwardable.
Docs for Forwardable are here: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/forwardable/rdoc/Forwardable.html
The
Struct
clas can help you build such a class. The initializer takes the arguments one by one instead of as a hash, but it's easy to convert that:If you want to remain more generic, you can call
values_at(*self.class.members)
instead.My solution is similar to Marc-André Lafortune. The difference is that each value is deleted from the input hash as it is used to assign a member variable. Then the Struct-derived class can perform further processing on whatever may be left in the Hash. For instance, the JobRequest below retains any "extra" arguments from the Hash in an options field.
Please take a look at my gem, Valuable:
I use it for everything from search classes (EmployeeSearch, TimeEntrySearch) to reporting ( EmployeesWhoDidNotClockOutReport, ExecutiveSummaryReport) to presenters to API endpoints. If you add some ActiveModel bits you can easily hook these classes up to forms for gathering criteria. I hope you find it useful.
You don't need the constant, but I don't think you can eliminate symbol-to-string:
BTW, you might take a look (if you haven't already) at the
Struct
class generator class, it's somewhat similar to what you are doing, but no hash-type initialization (but I guess it wouldn't be hard to make adequate generator class).HasProperties
Trying to implement hurikhan's idea, this is what I came to:
As I'm not that proficient with metaprogramming, I made the answer community wiki so anyone's free to change the implementation.
Struct.hash_initialized
Expanding on Marc-Andre's answer, here is a generic,
Struct
based method to create hash-initialized classes: