dm_preferences 0.5.6

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