preferences 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|