Question

I'm using the rails-settings gem, and I'm trying to understand how you add functions to ActiveRecord classes (I'm building my own library for card games), and I noticed that this gem uses one of the Meta-programming techniques to add the function to the ActiveRecord::Base class (I'm far from Meta-programming master in ruby, but I'm trying to learn it)

module RailsSettings
  class Railtie < Rails::Railtie

    initializer 'rails_settings.initialize', :after => :after_initialize do
      Railtie.extend_active_record
    end

  end

  class Railtie
    def self.extend_active_record
      ActiveRecord::Base.class_eval do
        def self.has_settings
          class_eval do
            def settings
              RailsSettings::ScopedSettings.for_thing(self)
            end

            scope :with_settings, :joins => "JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                               settings.thing_type = '#{self.base_class.name}')",
                                  :select => "DISTINCT #{self.table_name}.*"

            scope :with_settings_for, lambda { |var| { :joins => "JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                                    settings.thing_type = '#{self.base_class.name}') AND
                                                                                    settings.var = '#{var}'" } }

            scope :without_settings, :joins => "LEFT JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                       settings.thing_type = '#{self.base_class.name}')",
                                     :conditions => 'settings.id IS NULL'

            scope :without_settings_for, lambda { |var| { :joins => "LEFT JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                                            settings.thing_type = '#{self.base_class.name}') AND
                                                                                            settings.var = '#{var}'",
                                                          :conditions => 'settings.id IS NULL' } }
          end
        end
      end
    end

  end
end

What I don't understand is why he uses class_eval on ActiveRecord::Base, wasn't it easier if he just open the ActiveRecord::Base class and define the functions? Specially that there's nothing dynamic in the block (What I mean by dynamic is when you do class_eval or instance_eval on a string containing variables)

something like this:

module ActiveRecord
  class Base
    def self.has_settings
      class_eval do
        def settings
          RailsSettings::ScopedSettings.for_thing(self)
        end

        scope :with_settings, :joins => "JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                           settings.thing_type = '#{self.base_class.name}')",
                              :select => "DISTINCT #{self.table_name}.*"

        scope :with_settings_for, lambda { |var| { :joins => "JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                                settings.thing_type = '#{self.base_class.name}') AND
                                                                                settings.var = '#{var}'" } }

        scope :without_settings, :joins => "LEFT JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                   settings.thing_type = '#{self.base_class.name}')",
                                 :conditions => 'settings.id IS NULL'

        scope :without_settings_for, lambda { |var| { :joins => "LEFT JOIN settings ON (settings.thing_id = #{self.table_name}.#{self.primary_key} AND
                                                                                        settings.thing_type = '#{self.base_class.name}') AND
                                                                                        settings.var = '#{var}'",
                                                      :conditions => 'settings.id IS NULL' } }
      end
    end
  end
end

I understand the second class_eval (before the def settings) is to define functions on the fly on every class that 'has_settings' right ? Same question here, I think he could use "def self.settings" instead of "class_eval.... def settings", no ?

Was it helpful?

Solution

What rails-settings code does is considered good practice: it only messes with third-party modules when it's explicitely asked to do so. This way you also keep the namespaces tidily separated and all your code remains in your modules.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top