Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add named scopes: #with_preferences and #without_preferences [#1 stat…

…e:resolved]
  • Loading branch information...
commit 9383afff5a6e53138e066a71d7aebdeb3a8907d8 1 parent 21db4f9
Aaron Pfeifer obrie authored
2  CHANGELOG.rdoc
View
@@ -1,5 +1,7 @@
== master
+* Add named scopes: #with_preferences and #without_preferences
+
== 0.3.1 / 2009-04-25
* Rename Preference#attribute to #name to avoid conflicts with reserved methods in ActiveRecord
2  app/models/preference.rb
View
@@ -29,7 +29,7 @@ def split_group(group = nil)
if group.is_a?(ActiveRecord::Base)
group_id, group_type = group.id, group.class.base_class.name.to_s
else
- group_id, group_type = nil, group
+ group_id, group_type = nil, group.is_a?(Symbol) ? group.to_s : group
end
[group_id, group_type]
79 lib/preferences.rb
View
@@ -74,6 +74,27 @@ module MacroMethods
# 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 <tt>prefers?</tt> and +preferred+ on a record,
@@ -126,6 +147,11 @@ def preference(name, *args)
after_save :update_preferences
+ # Named scopes
+ named_scope :with_preferences, lambda {|preferences| build_preference_scope(preferences)}
+ named_scope :without_preferences, lambda {|preferences| build_preference_scope(preferences, true)}
+
+ extend Preferences::ClassMethods
include Preferences::InstanceMethods
end
@@ -161,6 +187,59 @@ def preference(name, *args)
end
end
+ module ClassMethods #:nodoc:
+ # Generates the scope for looking under records with a specific set of
+ # preferences associated with them.
+ #
+ # Note thate this is a bit more complicated than usual since the preference
+ # definitions aren't in the database for joins, defaults need to be accounted
+ # for, and querying for the the presence of multiple preferences requires
+ # multiple joins.
+ def build_preference_scope(preferences, inverse = false)
+ joins = []
+ statements = []
+ values = []
+
+ # Flatten the preferences for easier processing
+ preferences = preferences.inject({}) do |result, (group, value)|
+ if value.is_a?(Hash)
+ value.each {|preference, value| result[[group, preference]] = value}
+ else
+ result[[nil, group]] = value
+ end
+ result
+ end
+
+ preferences.each do |(group, preference), value|
+ preference = preference.to_s
+ value = preference_definitions[preference.to_s].type_cast(value)
+ is_default = default_preferences[preference.to_s] == value
+
+ group_id, group_type = Preference.split_group(group)
+ table = "preferences_#{group_id}_#{group_type}_#{preference}"
+
+ # Since each preference is a different record, they need their own
+ # join so that the proper conditions can be set
+ joins << "LEFT JOIN preferences AS #{table} ON #{table}.owner_id = #{table_name}.#{primary_key} AND " + sanitize_sql(
+ "#{table}.owner_type" => base_class.name.to_s,
+ "#{table}.group_id" => group_id,
+ "#{table}.group_type" => group_type,
+ "#{table}.name" => preference
+ )
+
+ if inverse
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NOT NULL' : ' != ?') + (!is_default ? " OR #{table}.id IS NULL" : '')
+ else
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NULL' : ' = ?') + (is_default ? " OR #{table}.id IS NULL" : '')
+ end
+ values << value unless value.nil?
+ end
+
+ sql = statements.map! {|statement| "(#{statement})"} * ' AND '
+ {:joins => joins, :conditions => values.unshift(sql)}
+ end
+ end
+
module InstanceMethods
def self.included(base) #:nodoc:
base.class_eval do
148 test/functional/preferences_test.rb
View
@@ -424,3 +424,151 @@ def test_should_be_able_to_differentiate_between_groups
assert_equal 2, @user.stored_preferences.size
end
end
+
+class UserWithScopeTest < ActiveSupport::TestCase
+ def setup
+ @user = create_user
+ @customized_user = create_user(:login => 'customized',
+ :prefers_hot_salsa => false,
+ :preferred_color => 'blue',
+ :preferred_language => 'Latin'
+ )
+ @customized_user.prefers_hot_salsa = false, 'home'
+ @customized_user.preferred_color = 'blue', 'home'
+ @customized_user.preferred_language = 'Latin', 'home'
+ @customized_user.save!
+ end
+
+ def test_should_not_find_if_no_preference_matched
+ assert_equal [], User.with_preferences(:language => 'Italian')
+ end
+
+ def test_should_find_with_null_preference
+ assert_equal [@user], User.with_preferences(:hot_salsa => nil)
+ end
+
+ def test_should_find_with_default_preference
+ assert_equal [@user], User.with_preferences(:language => 'English')
+ end
+
+ def test_should_find_with_multiple_default_preferences
+ assert_equal [@user], User.with_preferences(:language => 'English', :dark_chocolate => true)
+ end
+
+ def test_should_find_with_custom_preference
+ assert_equal [@customized_user], User.with_preferences(:language => 'Latin')
+ end
+
+ def test_should_find_with_multiple_custom_preferences
+ assert_equal [@customized_user], User.with_preferences(:language => 'Latin', :color => 'blue')
+ end
+
+ def test_should_find_with_mixed_default_and_custom_preferences
+ assert_equal [@customized_user], User.with_preferences(:language => 'Latin', :dark_chocolate => true)
+ end
+
+ def test_should_find_with_default_group_preference
+ assert_equal [@user], User.with_preferences(:home => {:language => 'English'})
+ end
+
+ def test_should_find_with_multiple_default_group_preferences
+ assert_equal [@user], User.with_preferences(:home => {:language => 'English', :dark_chocolate => true})
+ end
+
+ def test_should_find_with_custom_group_preference
+ assert_equal [@customized_user], User.with_preferences(:home => {:language => 'Latin'})
+ end
+
+ def test_should_find_with_multiple_custom_group_preferences
+ assert_equal [@customized_user], User.with_preferences(:home => {:language => 'Latin', :color => 'blue'})
+ end
+
+ def test_should_find_with_mixed_default_and_custom_group_preferences
+ assert_equal [@customized_user], User.with_preferences(:home => {:language => 'Latin', :dark_chocolate => true})
+ end
+
+ def test_should_find_with_mixed_basic_and_group_preferences
+ @customized_user.preferred_language = 'English'
+ @customized_user.save!
+
+ assert_equal [@customized_user], User.with_preferences(:language => 'English', :home => {:language => 'Latin'})
+ end
+
+ def test_should_allow_chaining
+ assert_equal [@user], User.with_preferences(:language => 'English').with_preferences(:dark_chocolate => true)
+ end
+end
+
+class UserWithoutScopeTest < ActiveSupport::TestCase
+ def setup
+ @user = create_user
+ @customized_user = create_user(:login => 'customized',
+ :prefers_hot_salsa => false,
+ :preferred_color => 'blue',
+ :preferred_language => 'Latin'
+ )
+ @customized_user.prefers_hot_salsa = false, 'home'
+ @customized_user.preferred_color = 'blue', 'home'
+ @customized_user.preferred_language = 'Latin', 'home'
+ @customized_user.save!
+ end
+
+ def test_should_not_find_if_no_preference_matched
+ assert_equal [], User.without_preferences(:dark_chocolate => true)
+ end
+
+ def test_should_find_with_null_preference
+ assert_equal [@user], User.without_preferences(:hot_salsa => false)
+ end
+
+ def test_should_find_with_default_preference
+ assert_equal [@user], User.without_preferences(:language => 'Latin')
+ end
+
+ def test_should_find_with_multiple_default_preferences
+ assert_equal [@user], User.without_preferences(:language => 'Latin', :dark_chocolate => false)
+ end
+
+ def test_should_find_with_custom_preference
+ assert_equal [@customized_user], User.without_preferences(:language => 'English')
+ end
+
+ def test_should_find_with_multiple_custom_preferences
+ assert_equal [@customized_user], User.without_preferences(:language => 'English', :color => nil)
+ end
+
+ def test_should_find_with_mixed_default_and_custom_preferences
+ assert_equal [@customized_user], User.without_preferences(:language => 'English', :dark_chocolate => false)
+ end
+
+ def test_should_find_with_default_group_preference
+ assert_equal [@user], User.without_preferences(:home => {:language => 'Latin'})
+ end
+
+ def test_should_find_with_multiple_default_group_preferences
+ assert_equal [@user], User.without_preferences(:home => {:language => 'Latin', :dark_chocolate => false})
+ end
+
+ def test_should_find_with_custom_group_preference
+ assert_equal [@customized_user], User.without_preferences(:home => {:language => 'English'})
+ end
+
+ def test_should_find_with_multiple_custom_group_preferences
+ assert_equal [@customized_user], User.without_preferences(:home => {:language => 'English', :color => nil})
+ end
+
+ def test_should_find_with_mixed_default_and_custom_group_preferences
+ assert_equal [@customized_user], User.without_preferences(:home => {:language => 'English', :dark_chocolate => false})
+ end
+
+ def test_should_find_with_mixed_basic_and_group_preferences
+ @customized_user.preferred_language = 'English'
+ @customized_user.save!
+
+ assert_equal [@customized_user], User.without_preferences(:language => 'Latin', :home => {:language => 'English'})
+ end
+
+ def test_should_allow_chaining
+ assert_equal [@user], User.without_preferences(:language => 'Latin').without_preferences(:dark_chocolate => false)
+ end
+end
4 test/unit/preference_test.rb
View
@@ -114,6 +114,10 @@ def test_should_be_able_to_split_non_active_record_groups
assert_nil group_id
assert_equal 'car', group_type
+ group_id, group_type = Preference.split_group(:car)
+ assert_nil group_id
+ assert_equal 'car', group_type
+
group_id, group_type = Preference.split_group(10)
assert_nil group_id
assert_equal 10, group_type
Please sign in to comment.
Something went wrong with that request. Please try again.