preferences 0.3.1 → 0.4.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.rdoc +17 -0
- data/README.rdoc +41 -10
- data/Rakefile +19 -17
- data/app/models/preference.rb +1 -1
- data/generators/preferences/USAGE +5 -0
- data/generators/preferences/preferences_generator.rb +7 -0
- data/generators/preferences/templates/001_create_preferences.rb +16 -0
- data/lib/preferences.rb +334 -63
- data/lib/preferences/preference_definition.rb +12 -4
- data/test/app_root/app/models/user.rb +6 -5
- data/test/app_root/db/migrate/004_migrate_preferences_to_version_1.rb +2 -2
- data/test/functional/preferences_test.rb +1171 -240
- data/test/test_helper.rb +13 -0
- data/test/unit/preference_definition_test.rb +35 -1
- data/test/unit/preference_test.rb +5 -1
- metadata +18 -22
data/CHANGELOG.rdoc
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
== master
|
2
2
|
|
3
|
+
== 0.4.0 / 2010-03-07
|
4
|
+
|
5
|
+
* Add {preference}_changed?, {preference}_was, {preference}_changed, {preference}_will_change!, and reset_{preference}!
|
6
|
+
* Add #preferences_changed?, #preferences_changed, and #preference_changes
|
7
|
+
* Fix preferences that are reverted externally still getting stored
|
8
|
+
* Fix preference definition types not being used to typecast values
|
9
|
+
* No longer allow both group and non-group preferences to be looked up at once (except for named scopes)
|
10
|
+
* Add support for using Symbols to reference groups
|
11
|
+
* Fix #reload not causing unsaved preferences to get reset
|
12
|
+
* Raise exception if unknown preference is accessed
|
13
|
+
* Rename #set_preference to #write_preference
|
14
|
+
* Add caching of preference lookups
|
15
|
+
* Fix preferences being stored even if they didn't change
|
16
|
+
* Release gems via rake-gemcutter instead of rubyforge
|
17
|
+
* Add a generator for db migration to make installation a bit easier [Tim Lowrimore]
|
18
|
+
* Add named scopes: #with_preferences and #without_preferences
|
19
|
+
|
3
20
|
== 0.3.1 / 2009-04-25
|
4
21
|
|
5
22
|
* Rename Preference#attribute to #name to avoid conflicts with reserved methods in ActiveRecord
|
data/README.rdoc
CHANGED
@@ -40,6 +40,17 @@ a separate table and making it dead-simple to define and manage preferences.
|
|
40
40
|
|
41
41
|
== Usage
|
42
42
|
|
43
|
+
=== Installation
|
44
|
+
|
45
|
+
+preferences+ requires an additional database table to work. You can generate
|
46
|
+
a migration for this table like so:
|
47
|
+
|
48
|
+
script/generate preferences
|
49
|
+
|
50
|
+
Then simply migrate your database:
|
51
|
+
|
52
|
+
rake db:migrate
|
53
|
+
|
43
54
|
=== Defining preferences
|
44
55
|
|
45
56
|
To define the preferences for a model, you can do so right within the model:
|
@@ -101,8 +112,8 @@ Reader method:
|
|
101
112
|
user.preferred(:language) # => "English"
|
102
113
|
|
103
114
|
Write method:
|
104
|
-
user.
|
105
|
-
user.
|
115
|
+
user.write_preference(:hot_salsa, false) # => false
|
116
|
+
user.write_preference(:language, "English") # => "English"
|
106
117
|
|
107
118
|
=== Accessing all preferences
|
108
119
|
|
@@ -135,7 +146,7 @@ through an example:
|
|
135
146
|
car = Car.find(:first)
|
136
147
|
|
137
148
|
user.preferred_color = 'red', car
|
138
|
-
# user.
|
149
|
+
# user.write_preference(:color, 'red', car) # The generic way
|
139
150
|
|
140
151
|
This will create a color preference of "red" for the given car. In this way,
|
141
152
|
you can have "color" preferences for different records.
|
@@ -151,14 +162,13 @@ preferences by name. For example,
|
|
151
162
|
|
152
163
|
user = User.find(:first)
|
153
164
|
|
154
|
-
user.preferred_color = 'red',
|
155
|
-
user.preferred_color = 'tan',
|
165
|
+
user.preferred_color = 'red', :automobiles
|
166
|
+
user.preferred_color = 'tan', :clothing
|
156
167
|
|
157
|
-
user.preferred_color(
|
158
|
-
user.preferred_color(
|
159
|
-
|
160
|
-
user.preferences
|
161
|
-
user.preferences('automobiles') # => {"color"=>"red"}
|
168
|
+
user.preferred_color(:automobiles) # => "red"
|
169
|
+
user.preferred_color(:clothing) # => "tan"
|
170
|
+
|
171
|
+
user.preferences(:automobiles) # => {"color"=>"red"}
|
162
172
|
|
163
173
|
=== Saving preferences
|
164
174
|
|
@@ -171,6 +181,27 @@ Preferences are treated in a similar fashion to attributes. For example,
|
|
171
181
|
|
172
182
|
Preferences are stored in a separate table called "preferences".
|
173
183
|
|
184
|
+
=== Tracking changes
|
185
|
+
|
186
|
+
Similar to ActiveRecord attributes, unsaved changes to preferences can be
|
187
|
+
tracked. For example,
|
188
|
+
|
189
|
+
user.preferred_language # => "English"
|
190
|
+
user.preferred_language_changed? # => false
|
191
|
+
user.preferred_language = 'Spanish'
|
192
|
+
user.preferred_language_changed? # => true
|
193
|
+
user.preferred_language_was # => "English"
|
194
|
+
user.preferred_language_change # => ["English", "Spanish"]
|
195
|
+
user.reset_preferred_language!
|
196
|
+
user.preferred_language # => "English"
|
197
|
+
|
198
|
+
Assigning the same value leaves the preference unchanged:
|
199
|
+
|
200
|
+
user.preferred_language # => "English"
|
201
|
+
user.preferred_language = 'English'
|
202
|
+
user.preferred_language_changed? # => false
|
203
|
+
user.preferred_language_change # => nil
|
204
|
+
|
174
205
|
== Testing
|
175
206
|
|
176
207
|
Before you can run any tests, the following gem must be installed:
|
data/Rakefile
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
1
3
|
require 'rake/testtask'
|
2
4
|
require 'rake/rdoctask'
|
3
5
|
require 'rake/gempackagetask'
|
4
|
-
require 'rake/contrib/sshpublisher'
|
5
6
|
|
6
7
|
spec = Gem::Specification.new do |s|
|
7
8
|
s.name = 'preferences'
|
8
|
-
s.version = '0.
|
9
|
+
s.version = '0.4.0'
|
9
10
|
s.platform = Gem::Platform::RUBY
|
10
|
-
s.summary = 'Adds support for easily creating custom preferences for models'
|
11
|
+
s.summary = 'Adds support for easily creating custom preferences for ActiveRecord models'
|
12
|
+
s.description = s.summary
|
11
13
|
|
12
|
-
s.files = FileList['{app,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
14
|
+
s.files = FileList['{app,generators,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
|
13
15
|
s.require_path = 'lib'
|
14
16
|
s.has_rdoc = true
|
15
17
|
s.test_files = Dir['test/**/*_test.rb']
|
@@ -52,20 +54,27 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
|
|
52
54
|
rdoc.options << '--line-numbers' << '--inline-source'
|
53
55
|
rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
|
54
56
|
end
|
55
|
-
|
57
|
+
|
58
|
+
desc 'Generate a gemspec file.'
|
59
|
+
task :gemspec do
|
60
|
+
File.open("#{spec.name}.gemspec", 'w') do |f|
|
61
|
+
f.write spec.to_ruby
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
56
65
|
Rake::GemPackageTask.new(spec) do |p|
|
57
66
|
p.gem_spec = spec
|
58
|
-
p.need_tar = true
|
59
|
-
p.need_zip = true
|
60
67
|
end
|
61
68
|
|
62
69
|
desc 'Publish the beta gem.'
|
63
70
|
task :pgem => [:package] do
|
71
|
+
require 'rake/contrib/sshpublisher'
|
64
72
|
Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
|
65
73
|
end
|
66
74
|
|
67
75
|
desc 'Publish the API documentation.'
|
68
76
|
task :pdoc => [:rdoc] do
|
77
|
+
require 'rake/contrib/sshpublisher'
|
69
78
|
Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
|
70
79
|
end
|
71
80
|
|
@@ -74,15 +83,8 @@ task :publish => [:pgem, :pdoc, :release]
|
|
74
83
|
|
75
84
|
desc 'Publish the release files to RubyForge.'
|
76
85
|
task :release => [:gem, :package] do
|
77
|
-
require '
|
78
|
-
|
79
|
-
ruby_forge = RubyForge.new.configure
|
80
|
-
ruby_forge.login
|
86
|
+
require 'rake/gemcutter'
|
81
87
|
|
82
|
-
|
83
|
-
|
84
|
-
puts "Releasing #{File.basename(file)}..."
|
85
|
-
|
86
|
-
ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
|
87
|
-
end
|
88
|
+
Rake::Gemcutter::Tasks.new(spec)
|
89
|
+
Rake::Task['gem:push'].invoke
|
88
90
|
end
|
data/app/models/preference.rb
CHANGED
@@ -29,7 +29,7 @@ class Preference < ActiveRecord::Base
|
|
29
29
|
if group.is_a?(ActiveRecord::Base)
|
30
30
|
group_id, group_type = group.id, group.class.base_class.name.to_s
|
31
31
|
else
|
32
|
-
group_id, group_type = nil, group
|
32
|
+
group_id, group_type = nil, group.is_a?(Symbol) ? group.to_s : group
|
33
33
|
end
|
34
34
|
|
35
35
|
[group_id, group_type]
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class CreatePreferences < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :preferences do |t|
|
4
|
+
t.string :name, :null => false
|
5
|
+
t.references :owner, :polymorphic => true, :null => false
|
6
|
+
t.references :group, :polymorphic => true
|
7
|
+
t.string :value
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :preferences, [:owner_id, :owner_type, :name, :group_id, :group_type], :unique => true, :name => 'index_preferences_on_owner_and_name_and_preference'
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.down
|
14
|
+
drop_table :preferences
|
15
|
+
end
|
16
|
+
end
|
data/lib/preferences.rb
CHANGED
@@ -74,6 +74,27 @@ module Preferences
|
|
74
74
|
# specified for a record. This will not include default preferences
|
75
75
|
# unless they have been explicitly set.
|
76
76
|
#
|
77
|
+
# == Named scopes
|
78
|
+
#
|
79
|
+
# In addition to the above associations, the following named scopes get
|
80
|
+
# generated for the model:
|
81
|
+
# * +with_preferences+ - Finds all records with a given set of preferences
|
82
|
+
# * +without_preferences+ - Finds all records without a given set of preferences
|
83
|
+
#
|
84
|
+
# In addition to utilizing preferences stored in the database, each of the
|
85
|
+
# above scopes also take into account the defaults that have been defined
|
86
|
+
# for each preference.
|
87
|
+
#
|
88
|
+
# Example:
|
89
|
+
#
|
90
|
+
# User.with_preferences(:notifications => true)
|
91
|
+
# User.with_preferences(:notifications => true, :color => 'blue')
|
92
|
+
#
|
93
|
+
# # Searching with group preferences
|
94
|
+
# car = Car.find(:first)
|
95
|
+
# User.with_preferences(car => {:color => 'blue'})
|
96
|
+
# User.with_preferences(:notifications => true, car => {:color => 'blue'})
|
97
|
+
#
|
77
98
|
# == Generated accessors
|
78
99
|
#
|
79
100
|
# In addition to calling <tt>prefers?</tt> and +preferred+ on a record,
|
@@ -87,10 +108,22 @@ module Preferences
|
|
87
108
|
# ...generates the following methods:
|
88
109
|
# * <tt>prefers_notifications?</tt> - Whether a value has been specified, i.e. <tt>record.prefers?(:notifications)</tt>
|
89
110
|
# * <tt>prefers_notifications</tt> - The actual value stored, i.e. <tt>record.prefers(:notifications)</tt>
|
90
|
-
# * <tt>prefers_notifications=(value)</tt> - Sets a new value, i.e. <tt>record.
|
91
|
-
# * <tt>
|
92
|
-
# * <tt>
|
93
|
-
# * <tt>
|
111
|
+
# * <tt>prefers_notifications=(value)</tt> - Sets a new value, i.e. <tt>record.write_preference(:notifications, value)</tt>
|
112
|
+
# * <tt>prefers_notifications_changed?</tt> - Whether the preference has unsaved changes
|
113
|
+
# * <tt>prefers_notifications_was</tt> - The last saved value for the preference
|
114
|
+
# * <tt>prefers_notifications_change</tt> - A list of [original_value, new_value] if the preference has changed
|
115
|
+
# * <tt>prefers_notifications_will_change!</tt> - Forces the preference to get updated
|
116
|
+
# * <tt>reset_prefers_notifications!</tt> - Reverts any unsaved changes to the preference
|
117
|
+
#
|
118
|
+
# ...and the equivalent +preferred+ methods:
|
119
|
+
# * <tt>preferred_notifications?</tt>
|
120
|
+
# * <tt>preferred_notifications</tt>
|
121
|
+
# * <tt>preferred_notifications=(value)</tt>
|
122
|
+
# * <tt>preferred_notifications_changed?</tt>
|
123
|
+
# * <tt>preferred_notifications_was</tt>
|
124
|
+
# * <tt>preferred_notifications_change</tt>
|
125
|
+
# * <tt>preferred_notifications_will_change!</tt>
|
126
|
+
# * <tt>reset_preferred_notifications!</tt>
|
94
127
|
#
|
95
128
|
# Notice that there are two tenses used depending on the context of the
|
96
129
|
# preference. Conventionally, <tt>prefers_notifications?</tt> is better
|
@@ -126,6 +159,11 @@ module Preferences
|
|
126
159
|
|
127
160
|
after_save :update_preferences
|
128
161
|
|
162
|
+
# Named scopes
|
163
|
+
named_scope :with_preferences, lambda {|preferences| build_preference_scope(preferences)}
|
164
|
+
named_scope :without_preferences, lambda {|preferences| build_preference_scope(preferences, true)}
|
165
|
+
|
166
|
+
extend Preferences::ClassMethods
|
129
167
|
include Preferences::InstanceMethods
|
130
168
|
end
|
131
169
|
|
@@ -153,14 +191,93 @@ module Preferences
|
|
153
191
|
|
154
192
|
# Writer
|
155
193
|
define_method("preferred_#{name}=") do |*args|
|
156
|
-
|
194
|
+
write_preference(*args.flatten.unshift(name))
|
157
195
|
end
|
158
196
|
alias_method "prefers_#{name}=", "preferred_#{name}="
|
159
197
|
|
198
|
+
# Changes
|
199
|
+
define_method("preferred_#{name}_changed?") do |*group|
|
200
|
+
preference_changed?(name, group.first)
|
201
|
+
end
|
202
|
+
alias_method "prefers_#{name}_changed?", "preferred_#{name}_changed?"
|
203
|
+
|
204
|
+
define_method("preferred_#{name}_was") do |*group|
|
205
|
+
preference_was(name, group.first)
|
206
|
+
end
|
207
|
+
alias_method "prefers_#{name}_was", "preferred_#{name}_was"
|
208
|
+
|
209
|
+
define_method("preferred_#{name}_change") do |*group|
|
210
|
+
preference_change(name, group.first)
|
211
|
+
end
|
212
|
+
alias_method "prefers_#{name}_change", "preferred_#{name}_change"
|
213
|
+
|
214
|
+
define_method("preferred_#{name}_will_change!") do |*group|
|
215
|
+
preference_will_change!(name, group.first)
|
216
|
+
end
|
217
|
+
alias_method "prefers_#{name}_will_change!", "preferred_#{name}_will_change!"
|
218
|
+
|
219
|
+
define_method("reset_preferred_#{name}!") do |*group|
|
220
|
+
reset_preference!(name, group.first)
|
221
|
+
end
|
222
|
+
alias_method "reset_prefers_#{name}!", "reset_preferred_#{name}!"
|
223
|
+
|
160
224
|
definition
|
161
225
|
end
|
162
226
|
end
|
163
227
|
|
228
|
+
module ClassMethods #:nodoc:
|
229
|
+
# Generates the scope for looking under records with a specific set of
|
230
|
+
# preferences associated with them.
|
231
|
+
#
|
232
|
+
# Note thate this is a bit more complicated than usual since the preference
|
233
|
+
# definitions aren't in the database for joins, defaults need to be accounted
|
234
|
+
# for, and querying for the the presence of multiple preferences requires
|
235
|
+
# multiple joins.
|
236
|
+
def build_preference_scope(preferences, inverse = false)
|
237
|
+
joins = []
|
238
|
+
statements = []
|
239
|
+
values = []
|
240
|
+
|
241
|
+
# Flatten the preferences for easier processing
|
242
|
+
preferences = preferences.inject({}) do |result, (group, value)|
|
243
|
+
if value.is_a?(Hash)
|
244
|
+
value.each {|preference, value| result[[group, preference]] = value}
|
245
|
+
else
|
246
|
+
result[[nil, group]] = value
|
247
|
+
end
|
248
|
+
result
|
249
|
+
end
|
250
|
+
|
251
|
+
preferences.each do |(group, preference), value|
|
252
|
+
preference = preference.to_s
|
253
|
+
value = preference_definitions[preference.to_s].type_cast(value)
|
254
|
+
is_default = default_preferences[preference.to_s] == value
|
255
|
+
|
256
|
+
group_id, group_type = Preference.split_group(group)
|
257
|
+
table = "preferences_#{group_id}_#{group_type}_#{preference}"
|
258
|
+
|
259
|
+
# Since each preference is a different record, they need their own
|
260
|
+
# join so that the proper conditions can be set
|
261
|
+
joins << "LEFT JOIN preferences AS #{table} ON #{table}.owner_id = #{table_name}.#{primary_key} AND " + sanitize_sql(
|
262
|
+
"#{table}.owner_type" => base_class.name.to_s,
|
263
|
+
"#{table}.group_id" => group_id,
|
264
|
+
"#{table}.group_type" => group_type,
|
265
|
+
"#{table}.name" => preference
|
266
|
+
)
|
267
|
+
|
268
|
+
if inverse
|
269
|
+
statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NOT NULL' : ' != ?') + (!is_default ? " OR #{table}.id IS NULL" : '')
|
270
|
+
else
|
271
|
+
statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NULL' : ' = ?') + (is_default ? " OR #{table}.id IS NULL" : '')
|
272
|
+
end
|
273
|
+
values << value unless value.nil?
|
274
|
+
end
|
275
|
+
|
276
|
+
sql = statements.map! {|statement| "(#{statement})"} * ' AND '
|
277
|
+
{:joins => joins, :conditions => values.unshift(sql)}
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
164
281
|
module InstanceMethods
|
165
282
|
def self.included(base) #:nodoc:
|
166
283
|
base.class_eval do
|
@@ -169,8 +286,8 @@ module Preferences
|
|
169
286
|
end
|
170
287
|
|
171
288
|
# Finds all preferences, including defaults, for the current record. If
|
172
|
-
#
|
173
|
-
#
|
289
|
+
# looking up custom group preferences, then this will include all default
|
290
|
+
# preferences within that particular group as well.
|
174
291
|
#
|
175
292
|
# == Examples
|
176
293
|
#
|
@@ -182,46 +299,23 @@ module Preferences
|
|
182
299
|
#
|
183
300
|
# A user with stored values for a particular group:
|
184
301
|
#
|
185
|
-
# user.preferred_color = 'red',
|
186
|
-
# user.preferences
|
187
|
-
# => {"language
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
# user.preferences(nil)
|
192
|
-
# => {"language"=>"English", "color"=>nil}
|
193
|
-
#
|
194
|
-
# Getting preference values for a particular group:
|
195
|
-
#
|
196
|
-
# user.preferences('cars')
|
197
|
-
# => {"language"=>"English", "color"=>"red"}
|
198
|
-
def preferences(*args)
|
199
|
-
if args.empty?
|
200
|
-
group = nil
|
201
|
-
conditions = {}
|
202
|
-
else
|
203
|
-
group = args.first
|
302
|
+
# user.preferred_color = 'red', :cars
|
303
|
+
# user.preferences(:cars)
|
304
|
+
# => {"language=>"English", "color"=>"red"}
|
305
|
+
def preferences(group = nil)
|
306
|
+
unless preferences_group_loaded?(group)
|
307
|
+
preferences = preferences_group(group)
|
204
308
|
|
205
|
-
# Split the actual group into its different parts (id/type) in case
|
206
|
-
# a record is passed in
|
207
309
|
group_id, group_type = Preference.split_group(group)
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
# Find all of the stored preferences
|
212
|
-
stored_preferences = self.stored_preferences.find(:all, :conditions => conditions)
|
213
|
-
|
214
|
-
# Hashify name -> value or group -> name -> value
|
215
|
-
stored_preferences.inject(self.class.default_preferences.dup) do |all_preferences, preference|
|
216
|
-
if !group && (preference_group = preference.group)
|
217
|
-
preferences = all_preferences[preference_group] ||= self.class.default_preferences.dup
|
218
|
-
else
|
219
|
-
preferences = all_preferences
|
310
|
+
find_preferences(:group_id => group_id, :group_type => group_type).each do |preference|
|
311
|
+
preferences[preference.name] ||= preference.value
|
220
312
|
end
|
221
313
|
|
222
|
-
|
223
|
-
|
314
|
+
# Add defaults
|
315
|
+
preferences.reverse_merge!(self.class.default_preferences.dup)
|
224
316
|
end
|
317
|
+
|
318
|
+
preferences_group(group).dup
|
225
319
|
end
|
226
320
|
|
227
321
|
# Queries whether or not a value is present for the given preference.
|
@@ -239,11 +333,12 @@ module Preferences
|
|
239
333
|
# user.preferred?(:color, 'cars') # => true
|
240
334
|
# user.preferred?(:color, Car.first) # => true
|
241
335
|
#
|
242
|
-
# user.
|
336
|
+
# user.write_preference(:color, nil)
|
243
337
|
# user.preferred(:color) # => nil
|
244
338
|
# user.preferred?(:color) # => false
|
245
339
|
def preferred?(name, group = nil)
|
246
340
|
name = name.to_s
|
341
|
+
assert_valid_preference(name)
|
247
342
|
|
248
343
|
value = preferred(name, group)
|
249
344
|
preference_definitions[name].query(value)
|
@@ -264,24 +359,27 @@ module Preferences
|
|
264
359
|
# user.preferred(:color, 'cars') # => "red"
|
265
360
|
# user.preferred(:color, Car.first) # => "red"
|
266
361
|
#
|
267
|
-
# user.
|
362
|
+
# user.write_preference(:color, 'blue')
|
268
363
|
# user.preferred(:color) # => "blue"
|
269
364
|
def preferred(name, group = nil)
|
270
365
|
name = name.to_s
|
366
|
+
assert_valid_preference(name)
|
271
367
|
|
272
|
-
if
|
368
|
+
if preferences_group(group).include?(name)
|
273
369
|
# Value for this group/name has been written, but not saved yet:
|
274
370
|
# grab from the pending values
|
275
|
-
value =
|
371
|
+
value = preferences_group(group)[name]
|
276
372
|
else
|
277
|
-
#
|
373
|
+
# Grab the first preference; if it doesn't exist, use the default value
|
278
374
|
group_id, group_type = Preference.split_group(group)
|
375
|
+
preference = find_preferences(:name => name, :group_id => group_id, :group_type => group_type).first unless preferences_group_loaded?(group)
|
279
376
|
|
280
|
-
# Grab the first preference; if it doesn't exist, use the default value
|
281
|
-
preference = stored_preferences.find(:first, :conditions => {:name => name, :group_id => group_id, :group_type => group_type})
|
282
377
|
value = preference ? preference.value : preference_definitions[name].default_value
|
378
|
+
preferences_group(group)[name] = value
|
283
379
|
end
|
284
380
|
|
381
|
+
definition = preference_definitions[name]
|
382
|
+
value = definition.type_cast(value) unless value.nil?
|
285
383
|
value
|
286
384
|
end
|
287
385
|
alias_method :prefers, :preferred
|
@@ -295,40 +393,213 @@ module Preferences
|
|
295
393
|
# == Examples
|
296
394
|
#
|
297
395
|
# user = User.find(:first)
|
298
|
-
# user.
|
396
|
+
# user.write_preference(:color, 'red') # => "red"
|
299
397
|
# user.save!
|
300
398
|
#
|
301
|
-
# user.
|
399
|
+
# user.write_preference(:color, 'blue', Car.first) # => "blue"
|
302
400
|
# user.save!
|
303
|
-
def
|
401
|
+
def write_preference(name, value, group = nil)
|
304
402
|
name = name.to_s
|
403
|
+
assert_valid_preference(name)
|
404
|
+
|
405
|
+
preferences_changed = preferences_changed_group(group)
|
406
|
+
if preferences_changed.include?(name)
|
407
|
+
old = preferences_changed[name]
|
408
|
+
preferences_changed.delete(name) unless preference_value_changed?(name, old, value)
|
409
|
+
else
|
410
|
+
old = clone_preference_value(name, group)
|
411
|
+
preferences_changed[name] = old if preference_value_changed?(name, old, value)
|
412
|
+
end
|
305
413
|
|
306
|
-
|
307
|
-
|
308
|
-
@preference_values[group][name] = value
|
414
|
+
value = convert_number_column_value(value) if preference_definitions[name].number?
|
415
|
+
preferences_group(group)[name] = value
|
309
416
|
|
310
417
|
value
|
311
418
|
end
|
312
419
|
|
420
|
+
# Whether any attributes have unsaved changes.
|
421
|
+
#
|
422
|
+
# == Examples
|
423
|
+
#
|
424
|
+
# user = User.find(:first)
|
425
|
+
# user.preferences_changed? # => false
|
426
|
+
# user.write_preference(:color, 'red')
|
427
|
+
# user.preferences_changed? # => true
|
428
|
+
# user.save
|
429
|
+
# user.preferences_changed? # => false
|
430
|
+
#
|
431
|
+
# # Groups
|
432
|
+
# user.preferences_changed?(:car) # => false
|
433
|
+
# user.write_preference(:color, 'red', :car)
|
434
|
+
# user.preferences_changed(:car) # => true
|
435
|
+
def preferences_changed?(group = nil)
|
436
|
+
!preferences_changed_group(group).empty?
|
437
|
+
end
|
438
|
+
|
439
|
+
# A list of the preferences that have unsaved changes.
|
440
|
+
#
|
441
|
+
# == Examples
|
442
|
+
#
|
443
|
+
# user = User.find(:first)
|
444
|
+
# user.preferences_changed # => []
|
445
|
+
# user.write_preference(:color, 'red')
|
446
|
+
# user.preferences_changed # => ["color"]
|
447
|
+
# user.save
|
448
|
+
# user.preferences_changed # => []
|
449
|
+
#
|
450
|
+
# # Groups
|
451
|
+
# user.preferences_changed(:car) # => []
|
452
|
+
# user.write_preference(:color, 'red', :car)
|
453
|
+
# user.preferences_changed(:car) # => ["color"]
|
454
|
+
def preferences_changed(group = nil)
|
455
|
+
preferences_changed_group(group).keys
|
456
|
+
end
|
457
|
+
|
458
|
+
# A map of the preferences that have changed in the current object.
|
459
|
+
#
|
460
|
+
# == Examples
|
461
|
+
#
|
462
|
+
# user = User.find(:first)
|
463
|
+
# user.preferred(:color) # => nil
|
464
|
+
# user.preference_changes # => {}
|
465
|
+
#
|
466
|
+
# user.write_preference(:color, 'red')
|
467
|
+
# user.preference_changes # => {"color" => [nil, "red"]}
|
468
|
+
# user.save
|
469
|
+
# user.preference_changes # => {}
|
470
|
+
#
|
471
|
+
# # Groups
|
472
|
+
# user.preferred(:color, :car) # => nil
|
473
|
+
# user.preference_changes(:car) # => {}
|
474
|
+
# user.write_preference(:color, 'red', :car)
|
475
|
+
# user.preference_changes(:car) # => {"color" => [nil, "red"]}
|
476
|
+
def preference_changes(group = nil)
|
477
|
+
preferences_changed(group).inject({}) do |changes, preference|
|
478
|
+
changes[preference] = preference_change(preference, group)
|
479
|
+
changes
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Reloads the pereferences of this object as well as its attributes
|
484
|
+
def reload(*args) #:nodoc:
|
485
|
+
result = super
|
486
|
+
|
487
|
+
@preferences.clear if @preferences
|
488
|
+
@preferences_changed.clear if @preferences_changed
|
489
|
+
|
490
|
+
result
|
491
|
+
end
|
492
|
+
|
313
493
|
private
|
494
|
+
# Asserts that the given name is a valid preference in this model. If it
|
495
|
+
# is not, then an ArgumentError exception is raised.
|
496
|
+
def assert_valid_preference(name)
|
497
|
+
raise(ArgumentError, "Unknown preference: #{name}") unless preference_definitions.include?(name)
|
498
|
+
end
|
499
|
+
|
500
|
+
# Gets the set of preferences identified by the given group
|
501
|
+
def preferences_group(group)
|
502
|
+
@preferences ||= {}
|
503
|
+
@preferences[group.is_a?(Symbol) ? group.to_s : group] ||= {}
|
504
|
+
end
|
505
|
+
|
506
|
+
# Determines whether the given group of preferences has already been
|
507
|
+
# loaded from the database
|
508
|
+
def preferences_group_loaded?(group)
|
509
|
+
preference_definitions.length == preferences_group(group).length
|
510
|
+
end
|
511
|
+
|
512
|
+
# Generates a clone of the current value stored for the preference with
|
513
|
+
# the given name / group
|
514
|
+
def clone_preference_value(name, group)
|
515
|
+
value = preferred(name, group)
|
516
|
+
value.duplicable? ? value.clone : value
|
517
|
+
rescue TypeError, NoMethodError
|
518
|
+
value
|
519
|
+
end
|
520
|
+
|
521
|
+
# Keeps track of all preferences that have been changed so that they can
|
522
|
+
# be properly updated in the database. Maps group -> preference -> value.
|
523
|
+
def preferences_changed_group(group)
|
524
|
+
@preferences_changed ||= {}
|
525
|
+
@preferences_changed[group.is_a?(Symbol) ? group.to_s : group] ||= {}
|
526
|
+
end
|
527
|
+
|
528
|
+
# Determines whether a preference changed in the given group
|
529
|
+
def preference_changed?(name, group)
|
530
|
+
preferences_changed_group(group).include?(name)
|
531
|
+
end
|
532
|
+
|
533
|
+
# Builds an array of [original_value, new_value] for the given preference.
|
534
|
+
# If the perference did not change, this will return nil.
|
535
|
+
def preference_change(name, group)
|
536
|
+
[preferences_changed_group(group)[name], preferred(name, group)] if preference_changed?(name, group)
|
537
|
+
end
|
538
|
+
|
539
|
+
# Gets the last saved value for the given preference
|
540
|
+
def preference_was(name, group)
|
541
|
+
preference_changed?(name, group) ? preferences_changed_group(group)[name] : preferred(name, group)
|
542
|
+
end
|
543
|
+
|
544
|
+
# Forces the given preference to be saved regardless of whether the value
|
545
|
+
# is actually diferent
|
546
|
+
def preference_will_change!(name, group)
|
547
|
+
preferences_changed_group(group)[name] = clone_preference_value(name, group)
|
548
|
+
end
|
549
|
+
|
550
|
+
# Reverts any unsaved changes to the given preference
|
551
|
+
def reset_preference!(name, group)
|
552
|
+
write_preference(name, preferences_changed_group(group)[name], group) if preference_changed?(name, group)
|
553
|
+
end
|
554
|
+
|
555
|
+
# Determines whether the old value is different from the new value for the
|
556
|
+
# given preference. This will use the typecasted value to determine
|
557
|
+
# equality.
|
558
|
+
def preference_value_changed?(name, old, value)
|
559
|
+
definition = preference_definitions[name]
|
560
|
+
if definition.type == :integer && (old.nil? || old == 0)
|
561
|
+
# For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
|
562
|
+
# Hence we don't record it as a change if the value changes from nil to ''.
|
563
|
+
# If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
|
564
|
+
# be typecast back to 0 (''.to_i => 0)
|
565
|
+
value = nil if value.blank?
|
566
|
+
else
|
567
|
+
value = definition.type_cast(value)
|
568
|
+
end
|
569
|
+
|
570
|
+
old != value
|
571
|
+
end
|
572
|
+
|
314
573
|
# Updates any preferences that have been changed/added since the record
|
315
574
|
# was last saved
|
316
575
|
def update_preferences
|
317
|
-
if @
|
318
|
-
@
|
576
|
+
if @preferences_changed
|
577
|
+
@preferences_changed.each do |group, preferences|
|
319
578
|
group_id, group_type = Preference.split_group(group)
|
320
579
|
|
321
|
-
|
322
|
-
attributes = {:name => name, :group_id => group_id, :group_type => group_type}
|
323
|
-
|
580
|
+
preferences.keys.each do |name|
|
324
581
|
# Find an existing preference or build a new one
|
325
|
-
|
326
|
-
preference
|
582
|
+
attributes = {:name => name, :group_id => group_id, :group_type => group_type}
|
583
|
+
preference = find_preferences(attributes).first || stored_preferences.build(attributes)
|
584
|
+
preference.value = preferred(name, group)
|
327
585
|
preference.save!
|
328
586
|
end
|
329
587
|
end
|
330
588
|
|
331
|
-
@
|
589
|
+
@preferences_changed.clear
|
590
|
+
end
|
591
|
+
end
|
592
|
+
|
593
|
+
# Finds all stored preferences with the given attributes. This will do a
|
594
|
+
# smart lookup by looking at the in-memory collection if it was eager-
|
595
|
+
# loaded.
|
596
|
+
def find_preferences(attributes)
|
597
|
+
if stored_preferences.loaded?
|
598
|
+
stored_preferences.select do |preference|
|
599
|
+
attributes.all? {|attribute, value| preference[attribute] == value}
|
600
|
+
end
|
601
|
+
else
|
602
|
+
stored_preferences.find(:all, :conditions => attributes)
|
332
603
|
end
|
333
604
|
end
|
334
605
|
end
|