module Preferences::MacroMethods

Public Instance Methods

preference(name, *args) click to toggle source

Defines a new preference for all records in the model. By default, preferences are assumed to have a boolean data type, so all values will be typecasted to true/false based on ActiveRecord rules.

Configuration options:

  • :default - The default value for the preference. Default is nil.

  • :group_defaults - Defines the default values to use for various groups. This should map group_name -> defaults. For ActiveRecord groups, use the class name.

Examples

The example below shows the various ways to define a preference for a particular model.

class User < ActiveRecord::Base
  preference :notifications, :default => false
  preference :color, :string, :default => 'red', :group_defaults => {:car => 'black'}
  preference :favorite_number, :integer
  preference :data, :any # Allows any data type to be stored
end

All preferences are also inherited by subclasses.

Associations

After the first preference is defined, the following associations are created for the model:

  • stored_preferences - A collection of all the custom preferences specified for a record. This will not include default preferences unless they have been explicitly set.

Named scopes

In addition to the above associations, the following named scopes get generated for the model:

  • with_preferences - Finds all records with a given set of preferences

  • without_preferences - Finds all records without a given set of preferences

In addition to utilizing preferences stored in the database, each of the above scopes also take into account the defaults that have been defined for each preference.

Example:

User.with_preferences(:notifications => true)
User.with_preferences(:notifications => true, :color => 'blue')

# Searching with group preferences
car = Car.find(:first)
User.with_preferences(car => {:color => 'blue'})
User.with_preferences(:notifications => true, car => {:color => 'blue'})

Generated accessors

In addition to calling prefers? and preferred on a record, you can also use the shortcut accessor methods that are generated when a preference is defined. For example,

class User < ActiveRecord::Base
  preference :notifications
end

…generates the following methods:

  • prefers_notifications? - Whether a value has been specified, i.e. record.prefers?(:notifications)

  • prefers_notifications - The actual value stored, i.e. record.prefers(:notifications)

  • prefers_notifications=(value) - Sets a new value, i.e. record.write_preference(:notifications, value)

  • prefers_notifications_changed? - Whether the preference has unsaved changes

  • prefers_notifications_was - The last saved value for the preference

  • prefers_notifications_change - A list of [original_value, new_value] if the preference has changed

  • prefers_notifications_will_change! - Forces the preference to get updated

  • reset_prefers_notifications! - Reverts any unsaved changes to the preference

…and the equivalent preferred methods:

  • preferred_notifications?

  • preferred_notifications

  • preferred_notifications=(value)

  • preferred_notifications_changed?

  • preferred_notifications_was

  • preferred_notifications_change

  • preferred_notifications_will_change!

  • reset_preferred_notifications!

Notice that there are two tenses used depending on the context of the preference. Conventionally, prefers_notifications? is better for accessing boolean preferences, while preferred_color is better for accessing non-boolean preferences.

Example:

user = User.find(:first)
user.prefers_notifications?         # => false
user.prefers_notifications          # => false
user.preferred_color?               # => true
user.preferred_color                # => 'red'
user.preferred_color = 'blue'       # => 'blue'

user.prefers_notifications = true

car = Car.find(:first)
user.preferred_color = 'red', car   # => 'red'
user.preferred_color(car)           # => 'red'
user.preferred_color?(car)          # => true

user.save!  # => true
    # File lib/preferences.rb
155 def preference(name, *args)
156   unless included_modules.include?(InstanceMethods)
157     class_attribute :preference_definitions
158     self.preference_definitions = {}
159 
160     has_many :stored_preferences, :as => :owner, :class_name => 'Preference', :dependent => :delete_all
161 
162     after_save :update_preferences
163 
164     # Named scopes
165     scope :with_preferences, lambda {|preferences| build_preference_scope(preferences)}
166     scope :without_preferences, lambda {|preferences| build_preference_scope(preferences, true)}
167 
168     extend Preferences::ClassMethods
169     include Preferences::InstanceMethods
170   end
171 
172   # Create the definition
173   name = name.to_s
174   definition = PreferenceDefinition.new(name, *args)
175   self.preference_definitions[name] = definition
176 
177   # Create short-hand accessor methods, making sure that the name
178   # is method-safe in terms of what characters are allowed
179   name = name.gsub(/[^A-Za-z0-9_-]/, '').underscore
180 
181   # Query lookup
182   define_method("preferred_#{name}?") do |*group|
183     preferred?(name, group.first)
184   end
185   alias_method "prefers_#{name}?", "preferred_#{name}?"
186 
187   # Reader
188   define_method("preferred_#{name}") do |*group|
189     preferred(name, group.first)
190   end
191   alias_method "prefers_#{name}", "preferred_#{name}"
192 
193   # Writer
194   define_method("preferred_#{name}=") do |*args|
195     write_preference(*args.flatten.unshift(name))
196   end
197   alias_method "prefers_#{name}=", "preferred_#{name}="
198 
199   # Changes
200   define_method("preferred_#{name}_changed?") do |*group|
201     preference_changed?(name, group.first)
202   end
203   alias_method "prefers_#{name}_changed?", "preferred_#{name}_changed?"
204 
205   define_method("preferred_#{name}_was") do |*group|
206     preference_was(name, group.first)
207   end
208   alias_method "prefers_#{name}_was", "preferred_#{name}_was"
209 
210   define_method("preferred_#{name}_change") do |*group|
211     preference_change(name, group.first)
212   end
213   alias_method "prefers_#{name}_change", "preferred_#{name}_change"
214 
215   define_method("preferred_#{name}_will_change!") do |*group|
216     preference_will_change!(name, group.first)
217   end
218   alias_method "prefers_#{name}_will_change!", "preferred_#{name}_will_change!"
219 
220   define_method("reset_preferred_#{name}!") do |*group|
221     reset_preference!(name, group.first)
222   end
223   alias_method "reset_prefers_#{name}!", "reset_preferred_#{name}!"
224 
225   definition
226 end