preferences 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,5 +1,20 @@
1
1
  *SVN*
2
2
 
3
+ *0.1.0* (June 19th, 2008)
4
+
5
+ * Avoid string evaluation for dynamic methods
6
+
7
+ * Return hashes for the preference_values, e.g.
8
+
9
+ user.preference_values # => {'color' => 'red', 'number' => 11, 'website' => {'background' => 'white', 'foreground' => 'black'}}
10
+ user.preferences_values('website') # => {'background' => 'white', 'foreground' => 'black'}
11
+
12
+ * Add more generic grouping of preferences than with just other records, e.g.
13
+
14
+ user.preferred_color('cars')
15
+
16
+ * Remove support for an options hash when specifying :for associations for preference
17
+
3
18
  *0.0.1* (May 10th, 2008)
4
19
 
5
20
  * Initial public release
data/README CHANGED
@@ -8,14 +8,18 @@ Wiki
8
8
 
9
9
  * http://wiki.pluginaweek.org/Preferences
10
10
 
11
- Source
11
+ API
12
12
 
13
- * http://svn.pluginaweek.org/trunk/plugins/preferences
13
+ * http://api.pluginaweek.org/preferences
14
14
 
15
15
  Development
16
16
 
17
17
  * http://dev.pluginaweek.org/browser/trunk/preferences
18
18
 
19
+ Source
20
+
21
+ * http://svn.pluginaweek.org/trunk/preferences
22
+
19
23
  == Description
20
24
 
21
25
  Preferences for models within an application, such as for users, is a pretty
@@ -23,11 +27,11 @@ common idiom. Although the rule of thumb is to keep the number of preferences
23
27
  available to a minimum, sometimes it's necessary if you want users to be able to
24
28
  disable things like e-mail notifications.
25
29
 
26
- Generally, basic preferences can be accomplish through simple designs, such as
30
+ Generally, basic preferences can be accomplished through simple designs, such as
27
31
  additional columns or a bit vector described and implemented by preference_fu[http://agilewebdevelopment.com/plugins/preferencefu].
28
32
  However, as you find the need for non-binary preferences and the number of
29
33
  preferences becomes unmanageable as individual columns in the database, the next
30
- step is often to create a seprate "preferences" table. This is where the +preferences+
34
+ step is often to create a separate "preferences" table. This is where the +preferences+
31
35
  plugin comes in.
32
36
 
33
37
  +preferences+ encapsulates this design by hiding the fact that preferences are
@@ -56,7 +60,7 @@ In the above model, 5 preferences have been defined:
56
60
  * language
57
61
 
58
62
  For each preference, a data type and default value can be specified. If no
59
- data type is given, it's considered a boolean value. If not default value is
63
+ data type is given, it's considered a boolean value. If no default value is
60
64
  given, the default is assumed to be nil.
61
65
 
62
66
  === Accessing preferences
@@ -84,7 +88,7 @@ Writer methods:
84
88
  ==== Generic methods
85
89
 
86
90
  Each shortcut method is essentially a wrapper for the various generic methods
87
- show below:
91
+ shown below:
88
92
 
89
93
  Query method:
90
94
  user.prefers?(:hot_salsa) # => false
@@ -100,21 +104,32 @@ Write method:
100
104
 
101
105
  === Accessing all preferences
102
106
 
103
- To get the collection of all preferences for a particular user, you can access
104
- the +preferences+ has_many association which is automatically generated:
107
+ To get the collection of all custom, stored preferences for a particular record,
108
+ you can access the +preferences+ has_many association which is automatically
109
+ generated:
105
110
 
106
111
  user.preferences
107
112
 
108
- === Preferences for other records
113
+ In addition to this, you can get a hash of all stored preferences *and* default
114
+ preferences, by accessing the +preference_values+ helper:
115
+
116
+ user.preference_values # => {"language"=>"English", "color"=>nil}
117
+
118
+ This hash will contain the value for every preference that has been defined for
119
+ the model, whether that's the default value or one that has been previously
120
+ stored.
121
+
122
+ === Grouping preferences
109
123
 
110
124
  In addition to defining generic preferences for the owning record, you can also
111
- define preferences for other records. This is best shown through an example:
125
+ group preferences by ActiveRecord objects or basic names. This is best shown
126
+ through an example:
112
127
 
113
128
  user = User.find(:first)
114
129
  car = Car.find(:first)
115
130
 
116
- user.preferred_color = 'red', {:for => car}
117
- # user.set_preference(:color, 'red', :for => car) # The generic way
131
+ user.preferred_color = 'red', car
132
+ # user.set_preference(:color, 'red', car) # The generic way
118
133
 
119
134
  This will create a preference for the color "red" for the given car. In this way,
120
135
  you can have "color" preferences for different records.
@@ -122,8 +137,22 @@ you can have "color" preferences for different records.
122
137
  To access the preference for a particular record, you can use the same accessor
123
138
  methods as before:
124
139
 
125
- user.preferred_color(:for => car)
126
- # user.preferred(:color, :for => car) # The generic way
140
+ user.preferred_color(car)
141
+ # user.preferred(:color, car) # The generic way
142
+
143
+ In addition to grouping preferences for a particular record, you can also group
144
+ preferences by name. For example,
145
+
146
+ user = User.find(:first)
147
+
148
+ user.preferred_color = 'red', 'automobiles'
149
+ user.preferred_color = 'tan', 'clothing'
150
+
151
+ user.preferred_color('automobiles') # => "red"
152
+ user.preferred_color('clothing') # => "tan"
153
+
154
+ user.preference_values # => {"color"=>nil, "automobiles"=>{"color"=>"red"}, "clothing=>{"color=>"tan"}}
155
+ user.preference_values('automobiles') # => {"color"=>"red"}
127
156
 
128
157
  === Saving preferences
129
158
 
@@ -134,13 +163,18 @@ are treated in a similar fashion to attributes. For example,
134
163
  user.attributes = {:preferred_color => 'red'}
135
164
  user.save!
136
165
 
137
- Preferences are stored in a separate table assumed to be called "preferences".
166
+ Preferences are stored in a separate table called "preferences".
138
167
 
139
168
  == Testing
140
169
 
141
170
  Before you can run any tests, the following gem must be installed:
142
171
  * plugin_test_helper[http://wiki.pluginaweek.org/Plugin_test_helper]
143
172
 
173
+ To run against a specific version of Rails:
174
+
175
+ rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
176
+
144
177
  == Dependencies
145
178
 
179
+ * Rails 2.1 or later
146
180
  * plugins_plus[http://wiki.pluginaweek.org/Plugins_plus]
data/Rakefile CHANGED
@@ -4,7 +4,7 @@ require 'rake/gempackagetask'
4
4
  require 'rake/contrib/sshpublisher'
5
5
 
6
6
  PKG_NAME = 'preferences'
7
- PKG_VERSION = '0.0.1'
7
+ PKG_VERSION = '0.1.0'
8
8
  PKG_FILE_NAME = "#{PKG_NAME}-#{PKG_VERSION}"
9
9
  RUBY_FORGE_PROJECT = 'pluginaweek'
10
10
 
@@ -68,7 +68,7 @@ desc 'Publish the release files to RubyForge.'
68
68
  task :release => [:gem, :package] do
69
69
  require 'rubyforge'
70
70
 
71
- ruby_forge = RubyForge.new
71
+ ruby_forge = RubyForge.new.configure
72
72
  ruby_forge.login
73
73
 
74
74
  %w( gem tgz zip ).each do |ext|
@@ -10,15 +10,27 @@
10
10
  class Preference < ActiveRecord::Base
11
11
  belongs_to :owner,
12
12
  :polymorphic => true
13
- belongs_to :preferenced,
13
+ belongs_to :group,
14
14
  :polymorphic => true
15
15
 
16
16
  validates_presence_of :attribute,
17
17
  :owner_id,
18
18
  :owner_type
19
- validates_presence_of :preferenced_id,
20
- :preferenced_type,
21
- :if => Proc.new {|p| p.preferenced_id? || p.preferenced_type?}
19
+ validates_presence_of :group_type,
20
+ :if => :group_id?
21
+
22
+ class << self
23
+ # Splits the given group into its corresponding id and type
24
+ def split_group(group = nil)
25
+ if group.is_a?(ActiveRecord::Base)
26
+ group_id, group_type = group.id, group.class.base_class.name.to_s
27
+ else
28
+ group_id, group_type = nil, group
29
+ end
30
+
31
+ return group_id, group_type
32
+ end
33
+ end
22
34
 
23
35
  # The definition for the attribute
24
36
  def definition
@@ -31,4 +43,10 @@ class Preference < ActiveRecord::Base
31
43
  value = definition.type_cast(value) if definition
32
44
  value
33
45
  end
46
+
47
+ # Only searches for the group record if the group id is specified
48
+ def group_with_optional_lookup
49
+ group_id ? group_without_optional_lookup : group_type
50
+ end
51
+ alias_method_chain :group, :optional_lookup
34
52
  end
data/lib/preferences.rb CHANGED
@@ -79,22 +79,26 @@ module PluginAWeek #:nodoc:
79
79
  # Example:
80
80
  #
81
81
  # user = User.find(:first)
82
- # user.prefers_notifications? # => false
83
- # user.prefers_color? # => true
84
- # user.preferred_color # => 'red'
85
- # user.preferred_color = 'blue' # => 'blue'
82
+ # user.prefers_notifications? # => false
83
+ # user.prefers_color? # => true
84
+ # user.preferred_color # => 'red'
85
+ # user.preferred_color = 'blue' # => 'blue'
86
86
  #
87
87
  # user.prefers_notifications = true
88
88
  #
89
89
  # car = Car.find(:first)
90
- # user.preferred_color = 'red', {:for => car} # => 'red'
91
- # user.preferred_color(:for => car) # => 'red'
92
- # user.prefers_color?(:for => car) # => true
90
+ # user.preferred_color = 'red', car # => 'red'
91
+ # user.preferred_color(car) # => 'red'
92
+ # user.prefers_color?(car) # => true
93
93
  #
94
94
  # user.save! # => true
95
95
  def preference(attribute, *args)
96
96
  unless included_modules.include?(InstanceMethods)
97
97
  class_inheritable_hash :preference_definitions
98
+ self.preference_definitions = {}
99
+
100
+ class_inheritable_hash :default_preference_values
101
+ self.default_preference_values = {}
98
102
 
99
103
  has_many :preferences,
100
104
  :as => :owner
@@ -107,73 +111,120 @@ module PluginAWeek #:nodoc:
107
111
  # Create the definition
108
112
  attribute = attribute.to_s
109
113
  definition = PreferenceDefinition.new(attribute, *args)
110
- self.preference_definitions = {attribute => definition}
114
+ self.preference_definitions[attribute] = definition
115
+ self.default_preference_values[attribute] = definition.default_value
111
116
 
112
117
  # Create short-hand helper methods, making sure that the attribute
113
118
  # is method-safe in terms of what characters are allowed
114
119
  attribute = attribute.gsub(/[^A-Za-z0-9_-]/, '').underscore
115
- class_eval <<-end_eval
116
- def prefers_#{attribute}?(options = {})
117
- prefers?(#{attribute.dump}, options)
118
- end
119
-
120
- def prefers_#{attribute}=(args)
121
- set_preference(*([#{attribute.dump}] + [args].flatten))
122
- end
123
-
124
- def preferred_#{attribute}(options = {})
125
- preferred(#{attribute.dump}, options)
126
- end
127
-
128
- alias_method :preferred_#{attribute}=, :prefers_#{attribute}=
129
- end_eval
120
+
121
+ # Query lookup
122
+ define_method("prefers_#{attribute}?") do |*group|
123
+ prefers?(attribute, group.first)
124
+ end
125
+
126
+ # Writer
127
+ define_method("prefers_#{attribute}=") do |*args|
128
+ set_preference(*([attribute] + [args].flatten))
129
+ end
130
+ alias_method "preferred_#{attribute}=", "prefers_#{attribute}="
131
+
132
+ # Reader
133
+ define_method("preferred_#{attribute}") do |*group|
134
+ preferred(attribute, group.first)
135
+ end
130
136
 
131
137
  definition
132
138
  end
133
139
  end
134
140
 
135
141
  module InstanceMethods
142
+ # Finds all preferences, including defaults, for the current record. If
143
+ # any custom group preferences have been stored, then this will include
144
+ # all default preferences within that particular group.
145
+ #
146
+ # == Examples
147
+ #
148
+ # A user with no stored values:
149
+ # user = User.find(:first)
150
+ # user.preference_values
151
+ # => {"language"=>"English", "color"=>nil}
152
+ #
153
+ # A user with stored values for a particular group:
154
+ # user.preferred_color = 'red', 'cars'
155
+ # user.preference_values
156
+ # => {"language"=>"English", "color"=>nil, "cars"=>{"language=>"English", "color"=>"red"}}
157
+ #
158
+ # Getting preference values for the owning record:
159
+ # user.preference_values(nil)
160
+ # => {"language"=>"English", "color"=>nil}
161
+ #
162
+ # Getting preference values for a particular group:
163
+ # user.preference_values('cars')
164
+ # => {"language"=>"English", "color"=>"red"}
165
+ def preference_values(*args)
166
+ if args.any?
167
+ group = args.first
168
+ group_id, group_type = Preference.split_group(group)
169
+ conditions = {:group_id => group_id, :group_type => group_type}
170
+ else
171
+ conditions = {}
172
+ end
173
+
174
+ # Find all of the stored preferences
175
+ stored_preferences = preferences.find(:all, :conditions => conditions)
176
+
177
+ # Hashify attribute -> value or group -> attribute -> value
178
+ stored_preferences.inject(self.class.default_preference_values.dup) do |preferences, preference|
179
+ if group = preference.group
180
+ preference_group = preferences[group] ||= self.class.default_preference_values.dup
181
+ else
182
+ preference_group = preferences
183
+ end
184
+
185
+ preference_group[preference.attribute] = preference.value
186
+ preferences
187
+ end
188
+ end
189
+
136
190
  # Queries whether or not a value has been specified for the given attribute.
137
191
  # This is dependent on how the value is type-casted.
138
192
  #
139
- # Configuration options:
140
- # * +for+ - The record being preferenced
141
- #
142
193
  # == Examples
143
194
  #
144
195
  # user = User.find(:first)
145
- # user.prefers?(:notifications) # => true
196
+ # user.prefers?(:notifications) # => true
197
+ #
198
+ # user.prefers(:notifications, 'error') # => true
146
199
  #
147
200
  # newsgroup = Newsgroup.find(:first)
148
- # user.prefers?(:notifications, :for => newsgroup) # => false
149
- def prefers?(attribute, options = {})
201
+ # user.prefers?(:notifications, newsgroup) # => false
202
+ def prefers?(attribute, group = nil)
150
203
  attribute = attribute.to_s
151
204
 
152
- value = preferred(attribute, options)
205
+ value = preferred(attribute, group)
153
206
  preference_definitions[attribute].query(value)
154
207
  end
155
208
 
156
209
  # Gets the preferred value for the given attribute.
157
210
  #
158
- # Configuration options:
159
- # * +for+ - The record being preferenced
160
- #
161
211
  # == Examples
162
212
  #
163
213
  # user = User.find(:first)
164
- # user.preferred(:color) # => 'red'
214
+ # user.preferred(:color) # => 'red'
215
+ #
216
+ # user.preferred(:color, 'cars') # => 'blue'
165
217
  #
166
218
  # car = Car.find(:first)
167
- # user.preferred(:color, :for => car) # => 'black'
168
- def preferred(attribute, options = {})
169
- options.assert_valid_keys(:for)
219
+ # user.preferred(:color, car) # => 'black'
220
+ def preferred(attribute, group = nil)
170
221
  attribute = attribute.to_s
171
222
 
172
- if @preference_values && @preference_values[attribute] && @preference_values[attribute].include?(options[:for])
173
- value = @preference_values[attribute][options[:for]]
223
+ if @preference_values && @preference_values[attribute] && @preference_values[attribute].include?(group)
224
+ value = @preference_values[attribute][group]
174
225
  else
175
- preferenced_id, preferenced_type = options[:for].id, options[:for].class.base_class.name.to_s if options[:for]
176
- preference = preferences.find(:first, :conditions => {:attribute => attribute, :preferenced_id => preferenced_id, :preferenced_type => preferenced_type})
226
+ group_id, group_type = Preference.split_group(group)
227
+ preference = preferences.find(:first, :conditions => {:attribute => attribute, :group_id => group_id, :group_type => group_type})
177
228
  value = preference ? preference.value : preference_definitions[attribute].default_value
178
229
  end
179
230
 
@@ -183,9 +234,6 @@ module PluginAWeek #:nodoc:
183
234
  # Sets a new value for the given attribute. The actual Preference record
184
235
  # is *not* created until the actual record is saved.
185
236
  #
186
- # Configuration options:
187
- # * +for+ - The record being preferenced
188
- #
189
237
  # == Examples
190
238
  #
191
239
  # user = User.find(:first)
@@ -193,15 +241,14 @@ module PluginAWeek #:nodoc:
193
241
  # user.save!
194
242
  #
195
243
  # newsgroup = Newsgroup.find(:first)
196
- # user.set_preference(:notifications, true, :for => newsgroup) # => true
244
+ # user.set_preference(:notifications, true, newsgroup) # => true
197
245
  # user.save!
198
- def set_preference(attribute, value, options = {})
199
- options.assert_valid_keys(:for)
246
+ def set_preference(attribute, value, group = nil)
200
247
  attribute = attribute.to_s
201
248
 
202
249
  @preference_values ||= {}
203
250
  @preference_values[attribute] ||= {}
204
- @preference_values[attribute][options[:for]] = value
251
+ @preference_values[attribute][group] = value
205
252
 
206
253
  value
207
254
  end
@@ -211,10 +258,10 @@ module PluginAWeek #:nodoc:
211
258
  # was last saved
212
259
  def update_preferences
213
260
  if @preference_values
214
- @preference_values.each do |attribute, preferenced_records|
215
- preferenced_records.each do |preferenced, value|
216
- preferenced_id, preferenced_type = preferenced.id, preferenced.class.base_class.name.to_s if preferenced
217
- attributes = {:attribute => attribute, :preferenced_id => preferenced_id, :preferenced_type => preferenced_type}
261
+ @preference_values.each do |attribute, grouped_records|
262
+ grouped_records.each do |group, value|
263
+ group_id, group_type = Preference.split_group(group)
264
+ attributes = {:attribute => attribute, :group_id => group_id, :group_type => group_type}
218
265
 
219
266
  # Find an existing preference or build a new one
220
267
  preference = preferences.find(:first, :conditions => attributes) || preferences.build(attributes)