public
Fork of brennandunn/preference_fu
Description: Allows the storage of a number of boolean fields with just one table column
Homepage: http://www.brennandunn.com
Clone URL: git://github.com/dynamix/preference_fu.git
Multiple preferences per model are now supported. (significant changes)
+ more tests
dynamix (author)
Tue Jul 08 13:37:31 -0700 2008
commit  fa1572ff92588c47934c65dc0d76627b043f52d9
tree    cf04ba6debc8a1eb79942c19e4185913660f2162
parent  e2ca7f0fa788cb221b208a428abc747a14e82fe7
0
...
1
2
 
 
3
4
5
6
 
 
7
8
9
10
11
12
 
13
14
15
16
17
 
18
19
20
21
 
22
23
 
24
25
26
...
32
33
34
35
 
 
36
37
 
 
38
39
40
...
42
43
44
45
 
46
47
48
 
49
50
51
52
 
53
54
55
56
57
 
 
 
58
59
60
 
61
62
63
64
 
65
66
 
67
68
69
...
73
74
75
76
77
78
 
79
...
 
 
1
2
3
4
5
 
6
7
8
9
10
11
12
13
14
15
16
17
18
 
19
20
 
 
 
21
22
 
23
24
25
26
...
32
33
34
 
35
36
37
 
38
39
40
41
42
...
44
45
46
 
47
48
49
 
50
51
52
53
 
54
55
56
57
 
 
58
59
60
61
62
 
63
64
65
66
 
67
68
 
69
70
71
72
...
76
77
78
 
79
80
81
82
0
@@ -1,26 +1,26 @@
0
-PreferenceFu
0
-============
0
+PreferenceFuu
0
+=============
0
 
0
 This plugin, greatly inspired by Jim Morris' blog post (http://blog.wolfman.com/articles/2007/08/07/bit-vector-preferences), aims to make it easy and flexible to store boolean preferences for an ActiveRecord model. This can be also used as a very quick way to setup an ACL.
0
 
0
-Because the values are stored within a bit vector, a virtually unlimited number of preferences can be created without additional migrations.
0
+Because the values are stored within a bit vector, a virtually unlimited number of preferences can be created without additional migrations.
0
+(for 32bit integer columns up to 32)
0
 
0
 Feel free to email me with any suggestions or problems.
0
 
0
 Blog: http://www.brennandunn.com
0
 Email address: me@brennandunn.com
0
 
0
+Modified by Martin Karlsch (martin@karlsch.com)
0
 
0
 Setup
0
 =====
0
 
0
-Simply add an integer column to each table of the database that requires preferences. By default, the column used is 'preferences', but by defining a method called preferences_column this can be modified.
0
+Simply add an integer column to each table of the database that requires preferences. By default, the column used is 'preferences', but the column name can be modified by passing :column => 'new_name' to the has_preferences call.
0
 
0
- def preferences_column
0
- 'other_column'
0
- end
0
+To allow multiple preferences per model the option :accessor => 'some_name' can be passed. If :column was not specified the column name to use is derived from the value given to accessor.
0
 
0
-Your migration should probably look something like this:
0
+schema like this:
0
   
0
   add_column :people, :preferences, :integer
0
     
0
@@ -32,9 +32,11 @@ Using PreferenceFu is very simple.
0
 
0
   class User < ActiveRecord::Base
0
   
0
- has_preferences :send_email, :change_theme, :delete_user, :create_user
0
+ has_preferences :send_email, :change_theme, :delete_user, :create_user,
0
+ :default => { :send_mail => true }
0
     
0
- set_default_preference :send_email, true
0
+ has_preferences :birthday, :holiday, :party,
0
+ :accessor => 'reminders'
0
   
0
   end
0
   
0
@@ -42,28 +44,29 @@ For new AR objects, all preference options will be set to false. This can be ove
0
 
0
 Setting a key:
0
   ...individually
0
- @user.prefs[:delete_user] = true
0
+ @user.preferences[:delete_user] = true
0
     
0
   ...mass assignment (useful with the params hash)
0
- @user.prefs = {:delete_user => true, :create_user => true}
0
+ @user.preferences = {:delete_user => true, :create_user => true}
0
   
0
 Setting an option as true doesn't necessarily need to be done with the Boolean true - in fact, the Fixnum 1, and strings '1', 'y' and 'yes' are all valid. This is particularly helpful for checkbox form posts.
0
 
0
- @user.prefs[:create_user] = 'yes'
0
+ @user.preferences[:create_user] = 'yes'
0
 
0
 
0
 Fetching a key:
0
- @user.prefs[:change_theme] => false
0
-
0
+ @user.preferences[:change_theme] => false
0
+ @user.preferences.change_theme? => false
0
+ @user.reminders.birthday? => false
0
 
0
 Getting the index of a key:
0
- @user.prefs.index(:delete_user) => 4
0
+ @user.reminders.index(:party) => 4
0
   
0
 
0
 Enumerable...
0
- @user.prefs.size => 4
0
+ @user.preferences.size => 4
0
   
0
- @user.prefs.each do |key, value|
0
+ @user.preferences.each do |key, value|
0
     puts "#{key} is set to #{value}"
0
   end
0
   
0
@@ -73,6 +76,6 @@ Warning
0
 
0
 This works by taking the index of the splat supplied in has_preferences as the power of two, summing all values, and storing the sum in the preferences column. Because of this, the first item in the splat will be identified by 1, the second by 2, the third by 4, etc. Once you start using PreferenceFu in production, add new options to the *end* of the splat. At the moment, there's no safe way to delete a preference item at the moment. Any advice is welcome!
0
 
0
-
0
 
0
 Copyright (c) 2008 Brennan Dunn, released under the MIT license
0
+Modifications Copyright (c) 2008 Martin Karlsch, released under the MIT license
0
\ No newline at end of file
...
1
2
3
4
5
6
7
 
8
9
10
11
12
13
14
15
16
17
18
 
 
 
 
 
19
20
21
22
23
24
25
 
 
 
26
27
 
28
29
30
31
32
 
 
33
34
35
 
 
 
 
 
 
 
 
 
 
 
36
37
38
39
 
 
 
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
 
 
 
75
76
77
78
79
80
81
82
83
84
 
85
86
 
 
 
87
88
89
90
91
...
100
101
102
103
 
104
105
106
...
131
132
133
134
 
135
136
137
...
154
155
156
157
158
 
 
159
160
161
...
1
2
3
 
 
 
 
4
5
6
7
8
 
 
 
 
 
 
 
9
10
11
12
13
14
 
 
 
 
 
 
15
16
17
18
 
19
20
 
 
 
 
21
22
23
24
 
25
26
27
28
29
30
31
32
33
34
35
36
 
 
 
37
38
39
40
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
42
43
44
45
46
47
 
48
49
50
51
 
52
53
 
54
55
56
57
 
58
59
60
...
69
70
71
 
72
73
74
75
...
100
101
102
 
103
104
105
106
...
123
124
125
 
 
126
127
128
129
130
0
@@ -1,91 +1,60 @@
0
 module PreferenceFu
0
   
0
   def self.included(receiver)
0
- #return if receiver.included_modules.include?(PreferenceFu::InstanceMethods)
0
-
0
- receiver.extend ClassMethods
0
- receiver.send :include, InstanceMethods
0
+ receiver.extend ClassMethods
0
   end
0
   
0
   module ClassMethods
0
     
0
- def preference_to_bitmask(preference)
0
- self.preference_options.each_key { |k| return k if self.preference_options[k][:key] == preference}
0
- return 0
0
- end
0
-
0
- def has_preferences(*options)
0
- alias_method_chain :initialize, :preferences
0
+ def has_preferences(*args)
0
+ options = args.last.is_a?(Hash) ? args.pop : {}
0
+ preference_accessor = options.delete(:accessor) || 'preferences'
0
+ column_name = options.delete(:column) || preference_accessor
0
+ defaults = options.delete(:default) || {}
0
       
0
- class_eval do
0
- class << self
0
- alias_method_chain :instantiate, :preferences
0
- attr_accessor :preference_options
0
- end
0
- end
0
+ metaclass.instance_exec(preference_accessor) { |preference_accessor|
0
+ attr_accessor "#{preference_accessor}_options"
0
+ }
0
       
0
- config = { :column => 'preferences' }
0
+ self.send("#{preference_accessor}_options=", preference_options = {})
0
       
0
- idx = 0; self.preference_options = {}
0
- options.each do |pref|
0
- self.preference_options[2**idx] = { :key => pref.to_sym, :default => false }
0
- idx += 1
0
+ args.each_with_index do |pref,idx|
0
+ preference_options[2**idx] = { :key => pref.to_sym, :default => defaults[pref.to_sym] || false }
0
       end
0
       
0
- class_eval <<-EOV
0
+ instance_code = <<-end_src
0
+ def initialize_with_#{preference_accessor}
0
+ initialize_without_#{preference_accessor}
0
+ #{preference_accessor}
0
+ yield self if block_given?
0
+ end
0
+
0
+ def #{preference_accessor}
0
+ @#{preference_accessor}_object ||= Preferences.new(read_attribute('#{column_name}'.to_sym),
0
+ self.class.#{preference_accessor}_options, '#{column_name}', self)
0
+ end
0
 
0
- def preferences_column
0
- '#{config[:column]}'
0
- end
0
+ def #{preference_accessor}=(hsh)
0
+ #{preference_accessor}.store(hsh)
0
+ end
0
 
0
- EOV
0
-
0
- end
0
-
0
- def set_default_preference(key, default)
0
- raise ArgumentError.new("Default value must be boolean") unless [true, false].include?(default)
0
- idx = preference_options.find { |idx, hsh| hsh[:key] == key.to_sym }.first rescue nil
0
- if idx
0
- preference_options[idx][:default] = default
0
- end
0
- end
0
-
0
- def instantiate_with_preferences(*args)
0
- record = instantiate_without_preferences(*args)
0
- record.prefs
0
- record
0
- end
0
-
0
- end
0
-
0
- module InstanceMethods
0
-
0
- def initialize_with_preferences(attributes = nil)
0
- initialize_without_preferences(attributes)
0
- prefs # use this to trigger update_permissions in Preferences
0
- yield self if block_given?
0
- end
0
-
0
- def prefs
0
- @preferences_object ||= Preferences.new(read_attribute(preferences_column.to_sym), self)
0
- end
0
-
0
- def prefs=(hsh)
0
- prefs.store(hsh)
0
+ end_src
0
+ class_eval(instance_code)
0
+ alias_method_chain :initialize, preference_accessor
0
     end
0
     
0
   end
0
   
0
-
0
   class Preferences
0
     
0
     include Enumerable
0
     
0
- attr_accessor :instance, :options
0
+ attr_accessor :options
0
     
0
- def initialize(prefs, instance)
0
+ def initialize(prefs, options,column,instance)
0
+ @options = options
0
+ @column = column
0
       @instance = instance
0
- @options = instance.class.preference_options
0
       
0
       # setup defaults if prefs is nil
0
       if prefs.nil?
0
@@ -100,7 +69,7 @@ module PreferenceFu
0
         raise(ArgumentError, "Input must be numeric")
0
       end
0
       
0
- update_permissions
0
+ update_preference_attribute
0
       
0
     end
0
     
0
@@ -131,7 +100,7 @@ module PreferenceFu
0
     def []=(key, value)
0
       idx, hsh = lookup(key)
0
       instance_variable_set("@#{key}", is_true(value))
0
- update_permissions
0
+ update_preference_attribute
0
     end
0
     
0
     def index(key)
0
@@ -154,8 +123,8 @@ module PreferenceFu
0
     
0
     private
0
     
0
- def update_permissions
0
- instance.write_attribute(instance.preferences_column, self.to_i)
0
+ def update_preference_attribute
0
+ @instance.write_attribute(@column, self.to_i)
0
       end
0
     
0
       def is_true(value)
...
15
16
17
 
 
 
 
 
 
18
19
20
...
15
16
17
18
19
20
21
22
23
24
25
26
0
@@ -15,6 +15,12 @@ def setup_db
0
     create_table :people do |t|
0
       t.string :name
0
       t.integer :preferences
0
+ t.integer :reminders
0
+ end
0
+
0
+ create_table :tasks do |t|
0
+ t.string :name
0
+ t.integer :reminders
0
     end
0
   end
0
 end
...
1
2
 
 
 
 
 
 
 
 
3
4
5
6
7
 
 
 
 
 
 
8
9
...
 
1
2
3
4
5
6
7
8
9
10
 
 
 
 
11
12
13
14
15
16
17
18
0
@@ -1,8 +1,17 @@
0
-
0
 class Person < ActiveRecord::Base
0
+
0
+ has_preferences :send_email, :change_theme, :delete_user, :create_user,
0
+ :column => 'preferences',
0
+ :accessor => :prefs,
0
+ :default => { :send_email => true }
0
+
0
+ has_preferences :birthday, :holiday,
0
+ :accessor => :reminders
0
   
0
- has_preferences :send_email, :change_theme, :delete_user, :create_user
0
-
0
- set_default_preference :send_email, true
0
-
0
+end
0
+
0
+class Task < ActiveRecord::Base
0
+ has_preferences :birthday, :holiday,
0
+ :accessor => :reminders,
0
+ :default => { :birthday => true, :holiday => true}
0
 end
0
\ No newline at end of file
...
8
9
10
 
11
12
13
...
57
58
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
...
8
9
10
11
12
13
14
...
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
0
@@ -8,6 +8,7 @@ class PreferenceFuTest < Test::Unit::TestCase
0
   def setup
0
     setup_db
0
     @person = Person.new
0
+ @task = Task.new
0
   end
0
 
0
   def teardown
0
@@ -57,4 +58,40 @@ class PreferenceFuTest < Test::Unit::TestCase
0
     assert_equal 4, @person.prefs.index(:delete_user)
0
   end
0
   
0
+ def test_second_preference
0
+ @person.reminders[:birthday] = true
0
+ @person.save
0
+ @new_person = Person.find(:first)
0
+ assert_equal [true, false], @new_person.reminders.map { |k, v| v }
0
+ end
0
+
0
+ def test_interference
0
+ @person.reminders = {:birthday => true,:holiday => true}
0
+ @person.prefs = {:send_email => false, :change_theme => false, :delete_user => false, :create_user => false}
0
+ @person.save
0
+ @new_person = Person.find(:first)
0
+ assert_equal [true, true], @new_person.reminders.map { |k, v| v }
0
+ assert_equal [false, false, false, false], @new_person.prefs.map { |k, v| v }
0
+
0
+ end
0
+
0
+
0
+ def test_access_by_method_missing
0
+ @person.reminders[:birthday] = true
0
+ assert_equal @person.reminders[:birthday], @person.reminders.birthday?
0
+ end
0
+
0
+ def test_preferences_on_different_models
0
+ @person.reminders[:birthday] = true
0
+ @task.reminders[:birthday] = false
0
+ assert_equal @person.reminders[:birthday], true
0
+ assert_equal @task.reminders[:birthday], false
0
+ @task.save
0
+ @person.save
0
+ @new_person = Person.find(:first)
0
+ assert_equal [true, false], @new_person.reminders.map { |k, v| v }
0
+ @new_task = Task.find(:first)
0
+ assert_equal [false, true], @new_task.reminders.map { |k, v| v }
0
+ end
0
+
0
 end

Comments

    No one has commented yet.