preferences 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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)