optimeez_preferences 0.4.2

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/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'preferences'
@@ -0,0 +1,5 @@
1
+ Usage:
2
+
3
+ script/generate preferences
4
+
5
+ This will create a migration that will add the proper table to store preferences.
@@ -0,0 +1,17 @@
1
+ require 'rails/generators/migration'
2
+
3
+ class PreferencesGenerator < Rails::Generators::NamedBase
4
+ include Rails::Generators::Migration
5
+
6
+ def install_preferences
7
+ migration_template "001_create_preferences.rb", "db/migrate/create_preferences"
8
+ end
9
+
10
+ def self.next_migration_number(path)
11
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
12
+ end
13
+
14
+ def self.source_root
15
+ @_devise_source_root ||= File.expand_path("../templates", __FILE__)
16
+ end
17
+ end
@@ -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
@@ -0,0 +1,619 @@
1
+ require 'preferences/preference_definition'
2
+
3
+ # Adds support for defining preferences on ActiveRecord models.
4
+ #
5
+ # == Saving preferences
6
+ #
7
+ # Preferences are not automatically saved when they are set. You must save
8
+ # the record that the preferences were set on.
9
+ #
10
+ # For example,
11
+ #
12
+ # class User < ActiveRecord::Base
13
+ # preference :notifications
14
+ # end
15
+ #
16
+ # u = User.new(:login => 'admin', :prefers_notifications => false)
17
+ # u.save!
18
+ #
19
+ # u = User.find_by_login('admin')
20
+ # u.attributes = {:prefers_notifications => true}
21
+ # u.save!
22
+ #
23
+ # == Validations
24
+ #
25
+ # Since the generated accessors for a preference allow the preference to be
26
+ # treated just like regular ActiveRecord attributes, they can also be
27
+ # validated against in the same way. For example,
28
+ #
29
+ # class User < ActiveRecord::Base
30
+ # preference :color, :string
31
+ #
32
+ # validates_presence_of :preferred_color
33
+ # validates_inclusion_of :preferred_color, :in => %w(red green blue)
34
+ # end
35
+ #
36
+ # u = User.new
37
+ # u.valid? # => false
38
+ # u.errors.on(:preferred_color) # => "can't be blank"
39
+ #
40
+ # u.preferred_color = 'white'
41
+ # u.valid? # => false
42
+ # u.errors.on(:preferred_color) # => "is not included in the list"
43
+ #
44
+ # u.preferred_color = 'red'
45
+ # u.valid? # => true
46
+ module Preferences
47
+ # Make sure your Gem loads the railtie.rb file if Rails is loaded first.
48
+ require 'preferences/engine' if defined?(Rails::Railtie)
49
+ module MacroMethods
50
+ # Defines a new preference for all records in the model. By default,
51
+ # preferences are assumed to have a boolean data type, so all values will
52
+ # be typecasted to true/false based on ActiveRecord rules.
53
+ #
54
+ # Configuration options:
55
+ # * <tt>:default</tt> - The default value for the preference. Default is nil.
56
+ # * <tt>:group_defaults</tt> - Defines the default values to use for various
57
+ # groups. This should map group_name -> defaults. For ActiveRecord groups,
58
+ # use the class name.
59
+ #
60
+ # == Examples
61
+ #
62
+ # The example below shows the various ways to define a preference for a
63
+ # particular model.
64
+ #
65
+ # class User < ActiveRecord::Base
66
+ # preference :notifications, :default => false
67
+ # preference :color, :string, :default => 'red', :group_defaults => {:car => 'black'}
68
+ # preference :favorite_number, :integer
69
+ # preference :data, :any # Allows any data type to be stored
70
+ # end
71
+ #
72
+ # All preferences are also inherited by subclasses.
73
+ #
74
+ # == Associations
75
+ #
76
+ # After the first preference is defined, the following associations are
77
+ # created for the model:
78
+ # * +stored_preferences+ - A collection of all the custom preferences
79
+ # specified for a record. This will not include default preferences
80
+ # unless they have been explicitly set.
81
+ #
82
+ # == Named scopes
83
+ #
84
+ # In addition to the above associations, the following named scopes get
85
+ # generated for the model:
86
+ # * +with_preferences+ - Finds all records with a given set of preferences
87
+ # * +without_preferences+ - Finds all records without a given set of preferences
88
+ #
89
+ # In addition to utilizing preferences stored in the database, each of the
90
+ # above scopes also take into account the defaults that have been defined
91
+ # for each preference.
92
+ #
93
+ # Example:
94
+ #
95
+ # User.with_preferences(:notifications => true)
96
+ # User.with_preferences(:notifications => true, :color => 'blue')
97
+ #
98
+ # # Searching with group preferences
99
+ # car = Car.find(:first)
100
+ # User.with_preferences(car => {:color => 'blue'})
101
+ # User.with_preferences(:notifications => true, car => {:color => 'blue'})
102
+ #
103
+ # == Generated accessors
104
+ #
105
+ # In addition to calling <tt>prefers?</tt> and +preferred+ on a record,
106
+ # you can also use the shortcut accessor methods that are generated when a
107
+ # preference is defined. For example,
108
+ #
109
+ # class User < ActiveRecord::Base
110
+ # preference :notifications
111
+ # end
112
+ #
113
+ # ...generates the following methods:
114
+ # * <tt>prefers_notifications?</tt> - Whether a value has been specified, i.e. <tt>record.prefers?(:notifications)</tt>
115
+ # * <tt>prefers_notifications</tt> - The actual value stored, i.e. <tt>record.prefers(:notifications)</tt>
116
+ # * <tt>prefers_notifications=(value)</tt> - Sets a new value, i.e. <tt>record.write_preference(:notifications, value)</tt>
117
+ # * <tt>prefers_notifications_changed?</tt> - Whether the preference has unsaved changes
118
+ # * <tt>prefers_notifications_was</tt> - The last saved value for the preference
119
+ # * <tt>prefers_notifications_change</tt> - A list of [original_value, new_value] if the preference has changed
120
+ # * <tt>prefers_notifications_will_change!</tt> - Forces the preference to get updated
121
+ # * <tt>reset_prefers_notifications!</tt> - Reverts any unsaved changes to the preference
122
+ #
123
+ # ...and the equivalent +preferred+ methods:
124
+ # * <tt>preferred_notifications?</tt>
125
+ # * <tt>preferred_notifications</tt>
126
+ # * <tt>preferred_notifications=(value)</tt>
127
+ # * <tt>preferred_notifications_changed?</tt>
128
+ # * <tt>preferred_notifications_was</tt>
129
+ # * <tt>preferred_notifications_change</tt>
130
+ # * <tt>preferred_notifications_will_change!</tt>
131
+ # * <tt>reset_preferred_notifications!</tt>
132
+ #
133
+ # Notice that there are two tenses used depending on the context of the
134
+ # preference. Conventionally, <tt>prefers_notifications?</tt> is better
135
+ # for accessing boolean preferences, while +preferred_color+ is better for
136
+ # accessing non-boolean preferences.
137
+ #
138
+ # Example:
139
+ #
140
+ # user = User.find(:first)
141
+ # user.prefers_notifications? # => false
142
+ # user.prefers_notifications # => false
143
+ # user.preferred_color? # => true
144
+ # user.preferred_color # => 'red'
145
+ # user.preferred_color = 'blue' # => 'blue'
146
+ #
147
+ # user.prefers_notifications = true
148
+ #
149
+ # car = Car.find(:first)
150
+ # user.preferred_color = 'red', car # => 'red'
151
+ # user.preferred_color(car) # => 'red'
152
+ # user.preferred_color?(car) # => true
153
+ #
154
+ # user.save! # => true
155
+ def preference(name, *args)
156
+ unless included_modules.include?(InstanceMethods)
157
+ class_inheritable_hash :preference_definitions
158
+ self.preference_definitions = {}
159
+
160
+ has_many :stored_preferences, :as => :owner, :class_name => 'Preference'
161
+
162
+ after_save :update_preferences
163
+
164
+ # Named scopes
165
+ scope :with_preferences, lambda {|preferences| build_preference_scope(preferences)}
166
+ scope :without_preferences, lambda {|preferences| build_preference_scope(preferences, true)}
167
+
168
+ extend Preferences::ClassMethods
169
+ include Preferences::InstanceMethods
170
+ end
171
+
172
+ # Create the definition
173
+ name = name.to_s
174
+ definition = PreferenceDefinition.new(name, *args)
175
+ self.preference_definitions[name] = definition
176
+
177
+ ######################################################## ALLEGER LA GEM PREFERENCE POUR ACCELERER LE CHARGEMENT DES MODELES #########
178
+ ######################################################## Gain de 70% (de 1,6s à 0,5s sur mac adrien avec 1 worker) #########
179
+ # # Create short-hand accessor methods, making sure that the name
180
+ # # is method-safe in terms of what characters are allowed
181
+ # name = name.gsub(/[^A-Za-z0-9_-]/, '').underscore
182
+ #
183
+ # # Query lookup
184
+ # define_method("preferred_#{name}?") do |*group|
185
+ # preferred?(name, group.first)
186
+ # end
187
+ # alias_method "prefers_#{name}?", "preferred_#{name}?"
188
+ #
189
+ # # Reader
190
+ # define_method("preferred_#{name}") do |*group|
191
+ # preferred(name, group.first)
192
+ # end
193
+ # alias_method "prefers_#{name}", "preferred_#{name}"
194
+ #
195
+ # # Writer
196
+ # define_method("preferred_#{name}=") do |*args|
197
+ # write_preference(*args.flatten.unshift(name))
198
+ # end
199
+ # alias_method "prefers_#{name}=", "preferred_#{name}="
200
+ #
201
+ # # Changes
202
+ # define_method("preferred_#{name}_changed?") do |*group|
203
+ # preference_changed?(name, group.first)
204
+ # end
205
+ # alias_method "prefers_#{name}_changed?", "preferred_#{name}_changed?"
206
+ #
207
+ # define_method("preferred_#{name}_was") do |*group|
208
+ # preference_was(name, group.first)
209
+ # end
210
+ # alias_method "prefers_#{name}_was", "preferred_#{name}_was"
211
+ #
212
+ # define_method("preferred_#{name}_change") do |*group|
213
+ # preference_change(name, group.first)
214
+ # end
215
+ # alias_method "prefers_#{name}_change", "preferred_#{name}_change"
216
+ #
217
+ # define_method("preferred_#{name}_will_change!") do |*group|
218
+ # preference_will_change!(name, group.first)
219
+ # end
220
+ # alias_method "prefers_#{name}_will_change!", "preferred_#{name}_will_change!"
221
+ #
222
+ # define_method("reset_preferred_#{name}!") do |*group|
223
+ # reset_preference!(name, group.first)
224
+ # end
225
+ # alias_method "reset_prefers_#{name}!", "reset_preferred_#{name}!"
226
+ #########################################################################################
227
+ definition
228
+ end
229
+ end
230
+
231
+ module ClassMethods #:nodoc:
232
+ # Generates the scope for looking under records with a specific set of
233
+ # preferences associated with them.
234
+ #
235
+ # Note thate this is a bit more complicated than usual since the preference
236
+ # definitions aren't in the database for joins, defaults need to be accounted
237
+ # for, and querying for the the presence of multiple preferences requires
238
+ # multiple joins.
239
+ def build_preference_scope(preferences, inverse = false)
240
+ joins = []
241
+ statements = []
242
+ values = []
243
+
244
+ # Flatten the preferences for easier processing
245
+ preferences = preferences.inject({}) do |result, (group, value)|
246
+ if value.is_a?(Hash)
247
+ value.each {|preference, value| result[[group, preference]] = value}
248
+ else
249
+ result[[nil, group]] = value
250
+ end
251
+ result
252
+ end
253
+
254
+ preferences.each do |(group, preference), value|
255
+ group_id, group_type = Preference.split_group(group)
256
+ preference = preference.to_s
257
+ definition = preference_definitions[preference.to_s]
258
+ value = definition.type_cast(value)
259
+ is_default = definition.default_value(group_type) == value
260
+
261
+ table = "preferences_#{group_id}_#{group_type}_#{preference}"
262
+
263
+ # Since each preference is a different record, they need their own
264
+ # join so that the proper conditions can be set
265
+ joins << "LEFT JOIN preferences AS #{table} ON #{table}.owner_id = #{table_name}.#{primary_key} AND " + sanitize_sql(
266
+ "#{table}.owner_type" => base_class.name.to_s,
267
+ "#{table}.group_id" => group_id,
268
+ "#{table}.group_type" => group_type,
269
+ "#{table}.name" => preference
270
+ )
271
+
272
+ if inverse
273
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NOT NULL' : ' != ?') + (!is_default ? " OR #{table}.id IS NULL" : '')
274
+ else
275
+ statements << "#{table}.id IS NOT NULL AND #{table}.value " + (value.nil? ? ' IS NULL' : ' = ?') + (is_default ? " OR #{table}.id IS NULL" : '')
276
+ end
277
+ values << value unless value.nil?
278
+ end
279
+
280
+ sql = statements.map! {|statement| "(#{statement})"} * ' AND '
281
+ {:joins => joins, :conditions => values.unshift(sql)}
282
+ end
283
+ end
284
+
285
+ module InstanceMethods
286
+ def self.included(base) #:nodoc:
287
+ base.class_eval do
288
+ alias_method :prefs, :preferences
289
+ end
290
+ end
291
+
292
+ # Finds all preferences, including defaults, for the current record. If
293
+ # looking up custom group preferences, then this will include all default
294
+ # preferences within that particular group as well.
295
+ #
296
+ # == Examples
297
+ #
298
+ # A user with no stored values:
299
+ #
300
+ # user = User.find(:first)
301
+ # user.preferences
302
+ # => {"language"=>"English", "color"=>nil}
303
+ #
304
+ # A user with stored values for a particular group:
305
+ #
306
+ # user.preferred_color = 'red', :cars
307
+ # user.preferences(:cars)
308
+ # => {"language=>"English", "color"=>"red"}
309
+ def preferences(group = nil)
310
+ preferences = preferences_group(group)
311
+
312
+ unless preferences_group_loaded?(group)
313
+ group_id, group_type = Preference.split_group(group)
314
+ find_preferences(:group_id => group_id, :group_type => group_type).each do |preference|
315
+ preferences[preference.name] = preference.value unless preferences.include?(preference.name)
316
+ end
317
+
318
+ # Add defaults
319
+ preference_definitions.each do |name, definition|
320
+ preferences[name] = definition.default_value(group_type) unless preferences.include?(name)
321
+ end
322
+ end
323
+
324
+ preferences.inject({}) do |typed_preferences, (name, value)|
325
+ typed_preferences[name] = value.nil? ? value : preference_definitions[name].type_cast(value)
326
+ typed_preferences
327
+ end
328
+ end
329
+
330
+ # Queries whether or not a value is present for the given preference.
331
+ # This is dependent on how the value is type-casted.
332
+ #
333
+ # == Examples
334
+ #
335
+ # class User < ActiveRecord::Base
336
+ # preference :color, :string, :default => 'red'
337
+ # end
338
+ #
339
+ # user = User.create
340
+ # user.preferred(:color) # => "red"
341
+ # user.preferred?(:color) # => true
342
+ # user.preferred?(:color, 'cars') # => true
343
+ # user.preferred?(:color, Car.first) # => true
344
+ #
345
+ # user.write_preference(:color, nil)
346
+ # user.preferred(:color) # => nil
347
+ # user.preferred?(:color) # => false
348
+ def preferred?(name, group = nil)
349
+ name = name.to_s
350
+ assert_valid_preference(name)
351
+
352
+ value = preferred(name, group)
353
+ preference_definitions[name].query(value)
354
+ end
355
+ alias_method :prefers?, :preferred?
356
+
357
+ # Gets the actual value stored for the given preference, or the default
358
+ # value if nothing is present.
359
+ #
360
+ # == Examples
361
+ #
362
+ # class User < ActiveRecord::Base
363
+ # preference :color, :string, :default => 'red'
364
+ # end
365
+ #
366
+ # user = User.create
367
+ # user.preferred(:color) # => "red"
368
+ # user.preferred(:color, 'cars') # => "red"
369
+ # user.preferred(:color, Car.first) # => "red"
370
+ #
371
+ # user.write_preference(:color, 'blue')
372
+ # user.preferred(:color) # => "blue"
373
+ def preferred(name, group = nil)
374
+ name = name.to_s
375
+ assert_valid_preference(name)
376
+
377
+ if preferences_group(group).include?(name)
378
+ # Value for this group/name has been written, but not saved yet:
379
+ # grab from the pending values
380
+ value = preferences_group(group)[name]
381
+ else
382
+ # Grab the first preference; if it doesn't exist, use the default value
383
+ group_id, group_type = Preference.split_group(group)
384
+ preference = find_preferences(:name => name, :group_id => group_id, :group_type => group_type).first unless preferences_group_loaded?(group)
385
+
386
+ value = preference ? preference.value : preference_definitions[name].default_value(group_type)
387
+ preferences_group(group)[name] = value
388
+ end
389
+
390
+ definition = preference_definitions[name]
391
+ value = definition.type_cast(value) unless value.nil?
392
+ value
393
+ end
394
+ alias_method :prefers, :preferred
395
+
396
+ # Sets a new value for the given preference. The actual Preference record
397
+ # is *not* created until this record is saved. In this way, preferences
398
+ # act *exactly* the same as attributes. They can be written to and
399
+ # validated against, but won't actually be written to the database until
400
+ # the record is saved.
401
+ #
402
+ # == Examples
403
+ #
404
+ # user = User.find(:first)
405
+ # user.write_preference(:color, 'red') # => "red"
406
+ # user.save!
407
+ #
408
+ # user.write_preference(:color, 'blue', Car.first) # => "blue"
409
+ # user.save!
410
+ def write_preference(name, value, group = nil)
411
+ name = name.to_s
412
+ assert_valid_preference(name)
413
+
414
+ preferences_changed = preferences_changed_group(group)
415
+ if preferences_changed.include?(name)
416
+ old = preferences_changed[name]
417
+ preferences_changed.delete(name) unless preference_value_changed?(name, old, value)
418
+ else
419
+ old = clone_preference_value(name, group)
420
+ preferences_changed[name] = old if preference_value_changed?(name, old, value)
421
+ end
422
+
423
+ value = convert_number_column_value(value) if preference_definitions[name].number?
424
+ preferences_group(group)[name] = value
425
+
426
+ value
427
+ end
428
+
429
+ # Whether any attributes have unsaved changes.
430
+ #
431
+ # == Examples
432
+ #
433
+ # user = User.find(:first)
434
+ # user.preferences_changed? # => false
435
+ # user.write_preference(:color, 'red')
436
+ # user.preferences_changed? # => true
437
+ # user.save
438
+ # user.preferences_changed? # => false
439
+ #
440
+ # # Groups
441
+ # user.preferences_changed?(:car) # => false
442
+ # user.write_preference(:color, 'red', :car)
443
+ # user.preferences_changed(:car) # => true
444
+ def preferences_changed?(group = nil)
445
+ !preferences_changed_group(group).empty?
446
+ end
447
+
448
+ # A list of the preferences that have unsaved changes.
449
+ #
450
+ # == Examples
451
+ #
452
+ # user = User.find(:first)
453
+ # user.preferences_changed # => []
454
+ # user.write_preference(:color, 'red')
455
+ # user.preferences_changed # => ["color"]
456
+ # user.save
457
+ # user.preferences_changed # => []
458
+ #
459
+ # # Groups
460
+ # user.preferences_changed(:car) # => []
461
+ # user.write_preference(:color, 'red', :car)
462
+ # user.preferences_changed(:car) # => ["color"]
463
+ def preferences_changed(group = nil)
464
+ preferences_changed_group(group).keys
465
+ end
466
+
467
+ # A map of the preferences that have changed in the current object.
468
+ #
469
+ # == Examples
470
+ #
471
+ # user = User.find(:first)
472
+ # user.preferred(:color) # => nil
473
+ # user.preference_changes # => {}
474
+ #
475
+ # user.write_preference(:color, 'red')
476
+ # user.preference_changes # => {"color" => [nil, "red"]}
477
+ # user.save
478
+ # user.preference_changes # => {}
479
+ #
480
+ # # Groups
481
+ # user.preferred(:color, :car) # => nil
482
+ # user.preference_changes(:car) # => {}
483
+ # user.write_preference(:color, 'red', :car)
484
+ # user.preference_changes(:car) # => {"color" => [nil, "red"]}
485
+ def preference_changes(group = nil)
486
+ preferences_changed(group).inject({}) do |changes, preference|
487
+ changes[preference] = preference_change(preference, group)
488
+ changes
489
+ end
490
+ end
491
+
492
+ # Reloads the pereferences of this object as well as its attributes
493
+ def reload(*args) #:nodoc:
494
+ result = super
495
+
496
+ @preferences.clear if @preferences
497
+ @preferences_changed.clear if @preferences_changed
498
+
499
+ result
500
+ end
501
+
502
+ private
503
+ # Asserts that the given name is a valid preference in this model. If it
504
+ # is not, then an ArgumentError exception is raised.
505
+ def assert_valid_preference(name)
506
+ raise(ArgumentError, "Unknown preference: #{name}") unless preference_definitions.include?(name)
507
+ end
508
+
509
+ # Gets the set of preferences identified by the given group
510
+ def preferences_group(group)
511
+ @preferences ||= {}
512
+ @preferences[group.is_a?(Symbol) ? group.to_s : group] ||= {}
513
+ end
514
+
515
+ # Determines whether the given group of preferences has already been
516
+ # loaded from the database
517
+ def preferences_group_loaded?(group)
518
+ preference_definitions.length == preferences_group(group).length
519
+ end
520
+
521
+ # Generates a clone of the current value stored for the preference with
522
+ # the given name / group
523
+ def clone_preference_value(name, group)
524
+ value = preferred(name, group)
525
+ value.duplicable? ? value.clone : value
526
+ rescue TypeError, NoMethodError
527
+ value
528
+ end
529
+
530
+ # Keeps track of all preferences that have been changed so that they can
531
+ # be properly updated in the database. Maps group -> preference -> value.
532
+ def preferences_changed_group(group)
533
+ @preferences_changed ||= {}
534
+ @preferences_changed[group.is_a?(Symbol) ? group.to_s : group] ||= {}
535
+ end
536
+
537
+ # Determines whether a preference changed in the given group
538
+ def preference_changed?(name, group)
539
+ preferences_changed_group(group).include?(name)
540
+ end
541
+
542
+ # Builds an array of [original_value, new_value] for the given preference.
543
+ # If the perference did not change, this will return nil.
544
+ def preference_change(name, group)
545
+ [preferences_changed_group(group)[name], preferred(name, group)] if preference_changed?(name, group)
546
+ end
547
+
548
+ # Gets the last saved value for the given preference
549
+ def preference_was(name, group)
550
+ preference_changed?(name, group) ? preferences_changed_group(group)[name] : preferred(name, group)
551
+ end
552
+
553
+ # Forces the given preference to be saved regardless of whether the value
554
+ # is actually diferent
555
+ def preference_will_change!(name, group)
556
+ preferences_changed_group(group)[name] = clone_preference_value(name, group)
557
+ end
558
+
559
+ # Reverts any unsaved changes to the given preference
560
+ def reset_preference!(name, group)
561
+ write_preference(name, preferences_changed_group(group)[name], group) if preference_changed?(name, group)
562
+ end
563
+
564
+ # Determines whether the old value is different from the new value for the
565
+ # given preference. This will use the typecasted value to determine
566
+ # equality.
567
+ def preference_value_changed?(name, old, value)
568
+ definition = preference_definitions[name]
569
+ if definition.type == :integer && (old.nil? || old == 0)
570
+ # For nullable numeric columns, NULL gets stored in database for blank (i.e. '') values.
571
+ # Hence we don't record it as a change if the value changes from nil to ''.
572
+ # If an old value of 0 is set to '' we want this to get changed to nil as otherwise it'll
573
+ # be typecast back to 0 (''.to_i => 0)
574
+ value = nil if value.blank?
575
+ else
576
+ value = definition.type_cast(value)
577
+ end
578
+
579
+ old != value
580
+ end
581
+
582
+ # Updates any preferences that have been changed/added since the record
583
+ # was last saved
584
+ def update_preferences
585
+ if @preferences_changed
586
+ @preferences_changed.each do |group, preferences|
587
+ group_id, group_type = Preference.split_group(group)
588
+
589
+ preferences.keys.each do |name|
590
+ # Find an existing preference or build a new one
591
+ attributes = {:name => name, :group_id => group_id, :group_type => group_type}
592
+ preference = find_preferences(attributes).first || stored_preferences.build(attributes)
593
+ preference.value = preferred(name, group)
594
+ preference.save!
595
+ end
596
+ end
597
+
598
+ @preferences_changed.clear
599
+ end
600
+ end
601
+
602
+ # Finds all stored preferences with the given attributes. This will do a
603
+ # smart lookup by looking at the in-memory collection if it was eager-
604
+ # loaded.
605
+ def find_preferences(attributes)
606
+ if stored_preferences.loaded?
607
+ stored_preferences.select do |preference|
608
+ attributes.all? {|attribute, value| preference[attribute] == value}
609
+ end
610
+ else
611
+ stored_preferences.find(:all, :conditions => attributes)
612
+ end
613
+ end
614
+ end
615
+ end
616
+
617
+ ActiveRecord::Base.class_eval do
618
+ extend Preferences::MacroMethods
619
+ end