pluginaweek-preferences 0.3.1

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