pluginaweek-preferences 0.3.1

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,56 @@
1
+ == master
2
+
3
+ == 0.3.1 / 2009-04-25
4
+
5
+ * Rename Preference#attribute to #name to avoid conflicts with reserved methods in ActiveRecord
6
+
7
+ == 0.3.0 / 2009-04-13
8
+
9
+ * Add dependency on Rails 2.3
10
+ * Remove dependency on plugins_plus
11
+
12
+ == 0.2.0 / 2008-12-14
13
+
14
+ * Remove the PluginAWeek namespace
15
+
16
+ == 0.1.5 / 2008-11-16
17
+
18
+ * Add all prefers/preferred accessors for preferences to be analogous to ActiveRecord column accessors
19
+ * Fix preferences defined in STI subclasses not working [Quinn Shanahan]
20
+
21
+ == 0.1.4 / 2008-10-26
22
+
23
+ * Change how the base module is included to prevent namespacing conflicts
24
+
25
+ == 0.1.3 / 2008-06-29
26
+
27
+ * Add +prefs+ as an alias for +preferences+
28
+ * Fix +preferences+ not properly selecting preferences when a group is specified
29
+ * Improve test coverage
30
+
31
+ == 0.1.2 / 2008-06-22
32
+
33
+ * Remove log files from gems
34
+
35
+ == 0.1.1 / 2008-06-20
36
+
37
+ * Rename preference_values hash to preferences
38
+ * Rename preferences association to stored_preferences
39
+
40
+ == 0.1.0 / 2008-06-19
41
+
42
+ * Avoid string evaluation for dynamic methods
43
+ * Return hashes for the preference_values, e.g.
44
+
45
+ user.preference_values # => {'color' => 'red', 'number' => 11, 'website' => {'background' => 'white', 'foreground' => 'black'}}
46
+ user.preference_values('website') # => {'background' => 'white', 'foreground' => 'black'}
47
+
48
+ * Add more generic grouping of preferences than with just other records, e.g.
49
+
50
+ user.preferred_color('cars')
51
+
52
+ * Remove support for an options hash when specifying :for associations for preference
53
+
54
+ == 0.0.1 / 2008-05-10
55
+
56
+ * Initial public release
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008-2009 Aaron Pfeifer
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,185 @@
1
+ == preferences
2
+
3
+ +preferences+ adds support for easily creating custom preferences for models.
4
+
5
+ == Resources
6
+
7
+ API
8
+
9
+ * http://api.pluginaweek.org/preferences
10
+
11
+ Bugs
12
+
13
+ * http://pluginaweek.lighthouseapp.com/projects/13286-preferences
14
+
15
+ Development
16
+
17
+ * http://github.com/pluginaweek/preferences
18
+
19
+ Source
20
+
21
+ * git://github.com/pluginaweek/preferences.git
22
+
23
+ == Description
24
+
25
+ Preferences for models within an application, such as for users, is a pretty
26
+ common idiom. Although the rule of thumb is to keep the number of preferences
27
+ available to a minimum, sometimes it's necessary if you want users to be able to
28
+ disable things like e-mail notifications.
29
+
30
+ Generally, basic preferences can be accomplished through simple designs, such as
31
+ additional columns or a bit vector described and implemented by preference_fu[http://agilewebdevelopment.com/plugins/preferencefu].
32
+ However, as you find the need for non-binary preferences and the number of
33
+ preferences becomes unmanageable as individual columns in the database, the next
34
+ step is often to create a separate "preferences" table. This is where the
35
+ +preferences+ plugin comes in.
36
+
37
+ +preferences+ encapsulates this design by exposing preferences using simple
38
+ attribute accessors on the model, hiding the fact that preferences are stored in
39
+ a separate table and making it dead-simple to define and manage preferences.
40
+
41
+ == Usage
42
+
43
+ === Defining preferences
44
+
45
+ To define the preferences for a model, you can do so right within the model:
46
+
47
+ class User < ActiveRecord::Base
48
+ preference :hot_salsa
49
+ preference :dark_chocolate, :default => true
50
+ preference :color, :string
51
+ preference :favorite_number
52
+ preference :language, :string, :default => 'English'
53
+ end
54
+
55
+ In the above model, 5 preferences have been defined:
56
+ * hot_salsa
57
+ * dark_chocolate
58
+ * color
59
+ * favorite_number
60
+ * language
61
+
62
+ For each preference, a data type and default value can be specified. If no
63
+ data type is given, it's assumed to be a boolean value. If no default value is
64
+ given, the default is assumed to be nil.
65
+
66
+ === Accessing preferences
67
+
68
+ Once preferences have been defined for a model, they can be accessed either
69
+ using the accessor methods that are generated for each preference or the generic
70
+ methods that are not specific to a particular preference.
71
+
72
+ ==== Accessors
73
+
74
+ There are several shortcut methods that are generated for each preference
75
+ defined on a model. These reflect the same set of methods (attribute accessors)
76
+ that are generated for a model's columns. Examples of these are shown below:
77
+
78
+ Query methods:
79
+ user.prefers_hot_salsa? # => false
80
+ user.preferred_language? # => true
81
+
82
+ Reader methods:
83
+ user.prefers_hot_salsa # => false
84
+ user.preferred_language # => "English"
85
+
86
+ Writer methods:
87
+ user.prefers_hot_salsa = false # => false
88
+ user.preferred_language = 'English' # => "English"
89
+
90
+ ==== Generic methods
91
+
92
+ Each preference accessor is essentially a wrapper for the various generic methods
93
+ shown below:
94
+
95
+ Query method:
96
+ user.prefers?(:hot_salsa) # => false
97
+ user.preferred?(:language) # => true
98
+
99
+ Reader method:
100
+ user.prefers(:hot_salsa) # => false
101
+ user.preferred(:language) # => "English"
102
+
103
+ Write method:
104
+ user.set_preference(:hot_salsa, false) # => false
105
+ user.set_preference(:language, "English") # => "English"
106
+
107
+ === Accessing all preferences
108
+
109
+ To get the collection of all custom, stored preferences for a particular record,
110
+ you can access the +stored_preferences+ has_many association which is automatically
111
+ generated:
112
+
113
+ user.stored_preferences
114
+
115
+ In addition to this, you can get a hash of all stored preferences *and* default
116
+ preferences, by accessing the +preferences+ helper:
117
+
118
+ user.preferences # => {"language"=>"English", "color"=>nil}
119
+
120
+ This hash will contain the value for every preference that has been defined for
121
+ the model, whether that's the default value or one that has been previously
122
+ stored.
123
+
124
+ A short-hand alternative for preferences is also available:
125
+
126
+ user.prefs # => {"language"=>"English", "color"=>nil}
127
+
128
+ === Grouping preferences
129
+
130
+ In addition to defining generic preferences for the owning record, you can also
131
+ group preferences by ActiveRecord objects or arbitrary names. This is best shown
132
+ through an example:
133
+
134
+ user = User.find(:first)
135
+ car = Car.find(:first)
136
+
137
+ user.preferred_color = 'red', car
138
+ # user.set_preference(:color, 'red', car) # The generic way
139
+
140
+ This will create a color preference of "red" for the given car. In this way,
141
+ you can have "color" preferences for different records.
142
+
143
+ To access the preference for a particular record, you can use the same accessor
144
+ methods as before:
145
+
146
+ user.preferred_color(car)
147
+ # user.preferred(:color, car) # The generic way
148
+
149
+ In addition to grouping preferences for a particular record, you can also group
150
+ preferences by name. For example,
151
+
152
+ user = User.find(:first)
153
+
154
+ user.preferred_color = 'red', 'automobiles'
155
+ user.preferred_color = 'tan', 'clothing'
156
+
157
+ user.preferred_color('automobiles') # => "red"
158
+ user.preferred_color('clothing') # => "tan"
159
+
160
+ user.preferences # => {"color"=>nil, "automobiles"=>{"color"=>"red"}, "clothing=>{"color=>"tan"}}
161
+ user.preferences('automobiles') # => {"color"=>"red"}
162
+
163
+ === Saving preferences
164
+
165
+ Note that preferences are not saved until the owning record is saved.
166
+ Preferences are treated in a similar fashion to attributes. For example,
167
+
168
+ user = user.find(:first)
169
+ user.attributes = {:prefers_hot_salsa => false, :preferred_color => 'red'}
170
+ user.save!
171
+
172
+ Preferences are stored in a separate table called "preferences".
173
+
174
+ == Testing
175
+
176
+ Before you can run any tests, the following gem must be installed:
177
+ * plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
178
+
179
+ To run against a specific version of Rails:
180
+
181
+ rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
182
+
183
+ == Dependencies
184
+
185
+ * Rails 2.3 or later
@@ -0,0 +1,96 @@
1
+ require 'rake/testtask'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/contrib/sshpublisher'
5
+
6
+ spec = Gem::Specification.new do |s|
7
+ s.name = 'preferences'
8
+ s.version = '0.3.1'
9
+ s.platform = Gem::Platform::RUBY
10
+ s.summary = 'Adds support for easily creating custom preferences for ActiveRecord models'
11
+ s.description = s.summary
12
+
13
+ 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.require_path = 'lib'
15
+ s.has_rdoc = true
16
+ s.test_files = Dir['test/**/*_test.rb']
17
+
18
+ s.author = 'Aaron Pfeifer'
19
+ s.email = 'aaron@pluginaweek.org'
20
+ s.homepage = 'http://www.pluginaweek.org'
21
+ s.rubyforge_project = 'pluginaweek'
22
+ end
23
+
24
+ desc 'Default: run all tests.'
25
+ task :default => :test
26
+
27
+ desc "Test the #{spec.name} plugin."
28
+ Rake::TestTask.new(:test) do |t|
29
+ t.libs << 'lib'
30
+ t.test_files = spec.test_files
31
+ t.verbose = true
32
+ end
33
+
34
+ begin
35
+ require 'rcov/rcovtask'
36
+ namespace :test do
37
+ desc "Test the #{spec.name} plugin with Rcov."
38
+ Rcov::RcovTask.new(:rcov) do |t|
39
+ t.libs << 'lib'
40
+ t.test_files = spec.test_files
41
+ t.rcov_opts << '--exclude="^(?!lib/|app/)"'
42
+ t.verbose = true
43
+ end
44
+ end
45
+ rescue LoadError
46
+ end
47
+
48
+ desc "Generate documentation for the #{spec.name} plugin."
49
+ Rake::RDocTask.new(:rdoc) do |rdoc|
50
+ rdoc.rdoc_dir = 'rdoc'
51
+ rdoc.title = spec.name
52
+ rdoc.template = '../rdoc_template.rb'
53
+ rdoc.options << '--line-numbers' << '--inline-source'
54
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
55
+ end
56
+
57
+ desc 'Generate a gemspec file.'
58
+ task :gemspec do
59
+ File.open("#{spec.name}.gemspec", 'w') do |f|
60
+ f.write spec.to_ruby
61
+ end
62
+ end
63
+
64
+ Rake::GemPackageTask.new(spec) do |p|
65
+ p.gem_spec = spec
66
+ p.need_tar = true
67
+ p.need_zip = true
68
+ end
69
+
70
+ desc 'Publish the beta gem.'
71
+ task :pgem => [:package] do
72
+ Rake::SshFilePublisher.new('aaron@pluginaweek.org', '/home/aaron/gems.pluginaweek.org/public/gems', 'pkg', "#{spec.name}-#{spec.version}.gem").upload
73
+ end
74
+
75
+ desc 'Publish the API documentation.'
76
+ task :pdoc => [:rdoc] do
77
+ Rake::SshDirPublisher.new('aaron@pluginaweek.org', "/home/aaron/api.pluginaweek.org/public/#{spec.name}", 'rdoc').upload
78
+ end
79
+
80
+ desc 'Publish the API docs and gem'
81
+ task :publish => [:pgem, :pdoc, :release]
82
+
83
+ desc 'Publish the release files to RubyForge.'
84
+ task :release => [:gem, :package] do
85
+ require 'rubyforge'
86
+
87
+ ruby_forge = RubyForge.new.configure
88
+ ruby_forge.login
89
+
90
+ %w(gem tgz zip).each do |ext|
91
+ file = "pkg/#{spec.name}-#{spec.version}.#{ext}"
92
+ puts "Releasing #{File.basename(file)}..."
93
+
94
+ ruby_forge.add_release(spec.rubyforge_project, spec.name, spec.version, file)
95
+ end
96
+ end
@@ -0,0 +1,65 @@
1
+ # Represents a preferred value for a particular preference on a model.
2
+ #
3
+ # == Grouped preferences
4
+ #
5
+ # In addition to simple named preferences, preferences can also be grouped by
6
+ # a particular value, be it a string or ActiveRecord object. For example, a
7
+ # User may have a preferred color for a particular Car. In this case, the
8
+ # +owner+ is the User record, the +name+ is "color", and the +group+ is the
9
+ # Car record. This allows preferences to have a sort of context around them.
10
+ class Preference < ActiveRecord::Base
11
+ belongs_to :owner, :polymorphic => true
12
+ belongs_to :group, :polymorphic => true
13
+
14
+ validates_presence_of :name, :owner_id, :owner_type
15
+ validates_presence_of :group_type, :if => :group_id?
16
+
17
+ class << self
18
+ # Splits the given group into its corresponding id and type. For simple
19
+ # primitives, the id will be nil. For complex types, specifically
20
+ # ActiveRecord objects, the id is the unique identifier stored in the
21
+ # database for the record.
22
+ #
23
+ # For example,
24
+ #
25
+ # Preference.split_group('google') # => [nil, "google"]
26
+ # Preference.split_group(1) # => [nil, 1]
27
+ # Preference.split_group(User.find(1)) # => [1, "User"]
28
+ def split_group(group = nil)
29
+ if group.is_a?(ActiveRecord::Base)
30
+ group_id, group_type = group.id, group.class.base_class.name.to_s
31
+ else
32
+ group_id, group_type = nil, group
33
+ end
34
+
35
+ [group_id, group_type]
36
+ end
37
+ end
38
+
39
+ # The definition of the preference as defined in the owner's model
40
+ def definition
41
+ # Optimize number of queries to the database by only looking up the actual
42
+ # owner record for STI cases when the definition can't be found in the
43
+ # stored owner type class
44
+ owner_type && (find_definition(owner_type.constantize) || find_definition(owner.class))
45
+ end
46
+
47
+ # Typecasts the value depending on the preference definition's declared type
48
+ def value
49
+ value = read_attribute(:value)
50
+ value = definition.type_cast(value) if definition
51
+ value
52
+ end
53
+
54
+ # Only searches for the group record if the group id is specified
55
+ def group_with_optional_lookup
56
+ group_id ? group_without_optional_lookup : group_type
57
+ end
58
+ alias_method_chain :group, :optional_lookup
59
+
60
+ private
61
+ # Finds the definition for this preference in the given owner class.
62
+ def find_definition(owner_class)
63
+ owner_class.respond_to?(:preference_definitions) && owner_class.preference_definitions[name]
64
+ end
65
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'preferences'
@@ -0,0 +1,339 @@
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
+ module MacroMethods
48
+ # Defines a new preference for all records in the model. By default,
49
+ # preferences are assumed to have a boolean data type, so all values will
50
+ # be typecasted to true/false based on ActiveRecord rules.
51
+ #
52
+ # Configuration options:
53
+ # * <tt>:default</tt> - The default value for the preference. Default is nil.
54
+ #
55
+ # == Examples
56
+ #
57
+ # The example below shows the various ways to define a preference for a
58
+ # particular model.
59
+ #
60
+ # class User < ActiveRecord::Base
61
+ # preference :notifications, :default => false
62
+ # preference :color, :string, :default => 'red'
63
+ # preference :favorite_number, :integer
64
+ # preference :data, :any # Allows any data type to be stored
65
+ # end
66
+ #
67
+ # All preferences are also inherited by subclasses.
68
+ #
69
+ # == Associations
70
+ #
71
+ # After the first preference is defined, the following associations are
72
+ # created for the model:
73
+ # * +stored_preferences+ - A collection of all the custom preferences
74
+ # specified for a record. This will not include default preferences
75
+ # unless they have been explicitly set.
76
+ #
77
+ # == Generated accessors
78
+ #
79
+ # In addition to calling <tt>prefers?</tt> and +preferred+ on a record,
80
+ # you can also use the shortcut accessor methods that are generated when a
81
+ # preference is defined. For example,
82
+ #
83
+ # class User < ActiveRecord::Base
84
+ # preference :notifications
85
+ # end
86
+ #
87
+ # ...generates the following methods:
88
+ # * <tt>prefers_notifications?</tt> - Whether a value has been specified, i.e. <tt>record.prefers?(:notifications)</tt>
89
+ # * <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.set_preference(:notifications, value)</tt>
91
+ # * <tt>preferred_notifications?</tt> - Whether a value has been specified, i.e. <tt>record.preferred?(:notifications)</tt>
92
+ # * <tt>preferred_notifications</tt> - The actual value stored, i.e. <tt>record.preferred(:notifications)</tt>
93
+ # * <tt>preferred_notifications=(value)</tt> - Sets a new value, i.e. <tt>record.set_preference(:notifications, value)</tt>
94
+ #
95
+ # Notice that there are two tenses used depending on the context of the
96
+ # preference. Conventionally, <tt>prefers_notifications?</tt> is better
97
+ # for accessing boolean preferences, while +preferred_color+ is better for
98
+ # accessing non-boolean preferences.
99
+ #
100
+ # Example:
101
+ #
102
+ # user = User.find(:first)
103
+ # user.prefers_notifications? # => false
104
+ # user.prefers_notifications # => false
105
+ # user.preferred_color? # => true
106
+ # user.preferred_color # => 'red'
107
+ # user.preferred_color = 'blue' # => 'blue'
108
+ #
109
+ # user.prefers_notifications = true
110
+ #
111
+ # car = Car.find(:first)
112
+ # user.preferred_color = 'red', car # => 'red'
113
+ # user.preferred_color(car) # => 'red'
114
+ # user.preferred_color?(car) # => true
115
+ #
116
+ # user.save! # => true
117
+ def preference(name, *args)
118
+ unless included_modules.include?(InstanceMethods)
119
+ class_inheritable_hash :preference_definitions
120
+ self.preference_definitions = {}
121
+
122
+ class_inheritable_hash :default_preferences
123
+ self.default_preferences = {}
124
+
125
+ has_many :stored_preferences, :as => :owner, :class_name => 'Preference'
126
+
127
+ after_save :update_preferences
128
+
129
+ include Preferences::InstanceMethods
130
+ end
131
+
132
+ # Create the definition
133
+ name = name.to_s
134
+ definition = PreferenceDefinition.new(name, *args)
135
+ self.preference_definitions[name] = definition
136
+ self.default_preferences[name] = definition.default_value
137
+
138
+ # Create short-hand accessor methods, making sure that the name
139
+ # is method-safe in terms of what characters are allowed
140
+ name = name.gsub(/[^A-Za-z0-9_-]/, '').underscore
141
+
142
+ # Query lookup
143
+ define_method("preferred_#{name}?") do |*group|
144
+ preferred?(name, group.first)
145
+ end
146
+ alias_method "prefers_#{name}?", "preferred_#{name}?"
147
+
148
+ # Reader
149
+ define_method("preferred_#{name}") do |*group|
150
+ preferred(name, group.first)
151
+ end
152
+ alias_method "prefers_#{name}", "preferred_#{name}"
153
+
154
+ # Writer
155
+ define_method("preferred_#{name}=") do |*args|
156
+ set_preference(*([name] + [args].flatten))
157
+ end
158
+ alias_method "prefers_#{name}=", "preferred_#{name}="
159
+
160
+ definition
161
+ end
162
+ end
163
+
164
+ module InstanceMethods
165
+ def self.included(base) #:nodoc:
166
+ base.class_eval do
167
+ alias_method :prefs, :preferences
168
+ end
169
+ end
170
+
171
+ # Finds all preferences, including defaults, for the current record. If
172
+ # any custom group preferences have been stored, then this will include
173
+ # all default preferences within that particular group.
174
+ #
175
+ # == Examples
176
+ #
177
+ # A user with no stored values:
178
+ #
179
+ # user = User.find(:first)
180
+ # user.preferences
181
+ # => {"language"=>"English", "color"=>nil}
182
+ #
183
+ # A user with stored values for a particular group:
184
+ #
185
+ # user.preferred_color = 'red', 'cars'
186
+ # user.preferences
187
+ # => {"language"=>"English", "color"=>nil, "cars"=>{"language=>"English", "color"=>"red"}}
188
+ #
189
+ # Getting preference values *just* for the owning record (i.e. excluding groups):
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
204
+
205
+ # Split the actual group into its different parts (id/type) in case
206
+ # a record is passed in
207
+ group_id, group_type = Preference.split_group(group)
208
+ conditions = {:group_id => group_id, :group_type => group_type}
209
+ end
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
220
+ end
221
+
222
+ preferences[preference.name] = preference.value
223
+ all_preferences
224
+ end
225
+ end
226
+
227
+ # Queries whether or not a value is present for the given preference.
228
+ # This is dependent on how the value is type-casted.
229
+ #
230
+ # == Examples
231
+ #
232
+ # class User < ActiveRecord::Base
233
+ # preference :color, :string, :default => 'red'
234
+ # end
235
+ #
236
+ # user = User.create
237
+ # user.preferred(:color) # => "red"
238
+ # user.preferred?(:color) # => true
239
+ # user.preferred?(:color, 'cars') # => true
240
+ # user.preferred?(:color, Car.first) # => true
241
+ #
242
+ # user.set_preference(:color, nil)
243
+ # user.preferred(:color) # => nil
244
+ # user.preferred?(:color) # => false
245
+ def preferred?(name, group = nil)
246
+ name = name.to_s
247
+
248
+ value = preferred(name, group)
249
+ preference_definitions[name].query(value)
250
+ end
251
+ alias_method :prefers?, :preferred?
252
+
253
+ # Gets the actual value stored for the given preference, or the default
254
+ # value if nothing is present.
255
+ #
256
+ # == Examples
257
+ #
258
+ # class User < ActiveRecord::Base
259
+ # preference :color, :string, :default => 'red'
260
+ # end
261
+ #
262
+ # user = User.create
263
+ # user.preferred(:color) # => "red"
264
+ # user.preferred(:color, 'cars') # => "red"
265
+ # user.preferred(:color, Car.first) # => "red"
266
+ #
267
+ # user.set_preference(:color, 'blue')
268
+ # user.preferred(:color) # => "blue"
269
+ def preferred(name, group = nil)
270
+ name = name.to_s
271
+
272
+ if @preference_values && @preference_values[group] && @preference_values[group].include?(name)
273
+ # Value for this group/name has been written, but not saved yet:
274
+ # grab from the pending values
275
+ value = @preference_values[group][name]
276
+ else
277
+ # Split the group being filtered
278
+ group_id, group_type = Preference.split_group(group)
279
+
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
+ value = preference ? preference.value : preference_definitions[name].default_value
283
+ end
284
+
285
+ value
286
+ end
287
+ alias_method :prefers, :preferred
288
+
289
+ # Sets a new value for the given preference. The actual Preference record
290
+ # is *not* created until this record is saved. In this way, preferences
291
+ # act *exactly* the same as attributes. They can be written to and
292
+ # validated against, but won't actually be written to the database until
293
+ # the record is saved.
294
+ #
295
+ # == Examples
296
+ #
297
+ # user = User.find(:first)
298
+ # user.set_preference(:color, 'red') # => "red"
299
+ # user.save!
300
+ #
301
+ # user.set_preference(:color, 'blue', Car.first) # => "blue"
302
+ # user.save!
303
+ def set_preference(name, value, group = nil)
304
+ name = name.to_s
305
+
306
+ @preference_values ||= {}
307
+ @preference_values[group] ||= {}
308
+ @preference_values[group][name] = value
309
+
310
+ value
311
+ end
312
+
313
+ private
314
+ # Updates any preferences that have been changed/added since the record
315
+ # was last saved
316
+ def update_preferences
317
+ if @preference_values
318
+ @preference_values.each do |group, new_preferences|
319
+ group_id, group_type = Preference.split_group(group)
320
+
321
+ new_preferences.each do |name, value|
322
+ attributes = {:name => name, :group_id => group_id, :group_type => group_type}
323
+
324
+ # Find an existing preference or build a new one
325
+ preference = stored_preferences.find(:first, :conditions => attributes) || stored_preferences.build(attributes)
326
+ preference.value = value
327
+ preference.save!
328
+ end
329
+ end
330
+
331
+ @preference_values = nil
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ ActiveRecord::Base.class_eval do
338
+ extend Preferences::MacroMethods
339
+ end