Search code examples
ruby-on-railsrubyruby-on-rails-plugins

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 leads 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

Solution

  • 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.