require 'preferences/preference_definition'
module PluginAWeek #:nodoc:
# Adds support for defining preferences on ActiveRecord models.
#
# == Saving preferences
#
# Preferences are not automatically saved when they are set. You must save
# the record that the preferences were set on.
#
# For example,
#
# class User < ActiveRecord::Base
# preference :notifications
# end
#
# u = User.new(:login => 'admin', :prefers_notifications => false)
# u.save!
#
# u = User.find_by_login('admin')
# u.attributes = {:prefers_notifications => true}
# u.save!
module Preferences
def self.included(base) #:nodoc:
base.class_eval do
extend PluginAWeek::Preferences::MacroMethods
end
end
module MacroMethods
# 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.
#
# == 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'
# 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:
# * +preferences+ - A collection of all the preferences specified for a record
#
# == Generated shortcut methods
#
# In addition to calling prefers? and +preferred+ on a record, you
# can also use the shortcut methods that are generated when a preference is
# defined. For example,
#
# class User < ActiveRecord::Base
# preference :notifications
# end
#
# ...generates the following methods:
# * prefers_notifications? - The same as calling record.prefers?(:notifications)
# * prefers_notifications=(value) - The same as calling record.set_preference(:notifications, value)
# * preferred_notifications - The same as called record.preferred(:notifications)
# * preferred_notifications=(value) - The same as calling record.set_preference(:notifications, value)
#
# Notice that there are two tenses used depending on the context of the
# preference. Conventionally, prefers_notifications? is better
# for boolean preferences, while +preferred_color+ is better for non-boolean
# preferences.
#
# Example:
#
# user = User.find(:first)
# user.prefers_notifications? # => false
# user.prefers_color? # => true
# user.preferred_color # => 'red'
# user.preferred_color = 'blue' # => 'blue'
#
# user.prefers_notifications = true
#
# car = Car.find(:first)
# user.preferred_color = 'red', {:for => car} # => 'red'
# user.preferred_color(:for => car) # => 'red'
# user.prefers_color?(:for => car) # => true
#
# user.save! # => true
def preference(attribute, *args)
unless included_modules.include?(InstanceMethods)
class_inheritable_hash :preference_definitions
has_many :preferences,
:as => :owner
after_save :update_preferences
include PluginAWeek::Preferences::InstanceMethods
end
# Create the definition
attribute = attribute.to_s
definition = PreferenceDefinition.new(attribute, *args)
self.preference_definitions = {attribute => definition}
# Create short-hand helper methods, making sure that the attribute
# is method-safe in terms of what characters are allowed
attribute = attribute.gsub(/[^A-Za-z0-9_-]/, '').underscore
class_eval <<-end_eval
def prefers_#{attribute}?(options = {})
prefers?(#{attribute.dump}, options)
end
def prefers_#{attribute}=(args)
set_preference(*([#{attribute.dump}] + [args].flatten))
end
def preferred_#{attribute}(options = {})
preferred(#{attribute.dump}, options)
end
alias_method :preferred_#{attribute}=, :prefers_#{attribute}=
end_eval
definition
end
end
module InstanceMethods
# Queries whether or not a value has been specified for the given attribute.
# This is dependent on how the value is type-casted.
#
# Configuration options:
# * +for+ - The record being preferenced
#
# == Examples
#
# user = User.find(:first)
# user.prefers?(:notifications) # => true
#
# newsgroup = Newsgroup.find(:first)
# user.prefers?(:notifications, :for => newsgroup) # => false
def prefers?(attribute, options = {})
attribute = attribute.to_s
value = preferred(attribute, options)
preference_definitions[attribute].query(value)
end
# Gets the preferred value for the given attribute.
#
# Configuration options:
# * +for+ - The record being preferenced
#
# == Examples
#
# user = User.find(:first)
# user.preferred(:color) # => 'red'
#
# car = Car.find(:first)
# user.preferred(:color, :for => car) # => 'black'
def preferred(attribute, options = {})
options.assert_valid_keys(:for)
attribute = attribute.to_s
if @preference_values && @preference_values[attribute] && @preference_values[attribute].include?(options[:for])
value = @preference_values[attribute][options[:for]]
else
preferenced_id, preferenced_type = options[:for].id, options[:for].class.base_class.name.to_s if options[:for]
preference = preferences.find(:first, :conditions => {:attribute => attribute, :preferenced_id => preferenced_id, :preferenced_type => preferenced_type})
value = preference ? preference.value : preference_definitions[attribute].default_value
end
value
end
# Sets a new value for the given attribute. The actual Preference record
# is *not* created until the actual record is saved.
#
# Configuration options:
# * +for+ - The record being preferenced
#
# == Examples
#
# user = User.find(:first)
# user.set_preference(:notifications, false) # => false
# user.save!
#
# newsgroup = Newsgroup.find(:first)
# user.set_preference(:notifications, true, :for => newsgroup) # => true
# user.save!
def set_preference(attribute, value, options = {})
options.assert_valid_keys(:for)
attribute = attribute.to_s
@preference_values ||= {}
@preference_values[attribute] ||= {}
@preference_values[attribute][options[:for]] = value
value
end
private
# Updates any preferences that have been changed/added since the record
# was last saved
def update_preferences
if @preference_values
@preference_values.each do |attribute, preferenced_records|
preferenced_records.each do |preferenced, value|
preferenced_id, preferenced_type = preferenced.id, preferenced.class.base_class.name.to_s if preferenced
attributes = {:attribute => attribute, :preferenced_id => preferenced_id, :preferenced_type => preferenced_type}
# Find an existing preference or build a new one
preference = preferences.find(:first, :conditions => attributes) || preferences.build(attributes)
preference.value = value
preference.save!
end
end
@preference_values = nil
end
end
end
end
end
ActiveRecord::Base.class_eval do
include PluginAWeek::Preferences
end