How do I access options passed to an acts_as plugin (ActiveRecord decorator) from instance methods?
At the moment I store each option in its own class attribute but this lea开发者_如何转开发ds to hard to read code when I need to access the passed options from instance methods.
For example if I pass a column name as an option I have to use self.send(self.class.path_finder_column)
to get the column value from an instance method.
Notice I have prefixed the class attribute with the name of my plugin to prevent name clashes.
Here is a simple code example of a plugin which is passed an option, column
, which is then accessed from the instance method set_path
. Can the getters/setters be simplified to be more readable?
# usage: path_find :column => 'path'
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
module InstanceMethods
def set_path
# setter
self.send(self.class.path_finder_column + '=', 'some value')
# getter
self.send(self.class.path_finder_column)
end
end
end
end
ActiveRecord::Base.send :extend, PathFinder
You can generate all those methods at runtime.
module PathFinder
def path_finder(options = {})
# Create class attributes for options
self.cattr_accessor :path_finder_options
self.path_finder_options = options
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
Unless you need to store the options, you can also delete the lines
self.cattr_accessor :path_finder_options
self.path_finder_options = options
Note that my solution doesn't need a setter and a getter as long as you always use path_finder
and path_finder=
.
So, the shortest solution is (assuming only the :column option and no other requirements)
module PathFinder
def path_finder(options = {})
# here more logic
# ...
class_eval <<-RUBY
def path_finder(value)
self.#{options[:column]} = value
end
def path_finder
self.#{options[:column]}
end
RUBY
end
end
ActiveRecord::Base.send :extend, PathFinder
This approach is similar to the one adopted by acts_as_list
and acts_as_tree
.
To start with cattr_accessor creates a class variable for each symbol it's given. In ruby, class variables have their names prefixed with @@.
So you can use @@path_finder_column
in place of self.class.path_finder_column
.
However that's a moot point considering what I'm going to suggest next.
In the specific case presented by the code in the question. The combination getter and setter you've defined doesn't fit ruby conventions. Seeing as how you're essentially rebranding the accessors generated for the path_finder_column with a generic name, you can reduce it all to just a pair of aliases.
Assuming there's an error in the combo accessor (how is the code supposed to know whether to get or set), The finalized module will look like this:
module PathFinder
def path_finder(options = {})
send :include, InstanceMethods
# Create class attributes for options
self.cattr_accessor :path_finder_column
self.path_finder_column = options[:column]
alias :set_path, path_finder_column
alias :set_path=, "#{path_finder_column}="
end
module InstanceMethods
# other instance methods here.
end
end
You can use cattr_accessor to store the configuration value at a class level and use in all your instance methods. You can see an example at http://github.com/smsohan/acts_as_permalinkable/blob/master/lib/active_record/acts/permalinkable.rb
The code to look at is this:
def acts_as_permalinkable(options = {})
send :cattr_accessor, :permalink_options
self.permalink_options = { :permalink_method => :name, :permalink_field_name => :permalink, :length => 200 }
self.permalink_options.update(options) if options.is_a?(Hash)
send :include, InstanceMethods
send :after_create, :generate_permalink
end
Hope it helps!
精彩评论