optimeez_preferences 0.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,82 @@
1
+ == master
2
+
3
+ == 0.4.2 / 2010-04-17
4
+
5
+ * Fix #preferences lookup not typecasting values
6
+
7
+ == 0.4.1 / 2010-03-07
8
+
9
+ * Add support for per-group default preferences
10
+ * Fix unsaved boolean preferences getting overridden by defaults if value is false
11
+
12
+ == 0.4.0 / 2010-03-07
13
+
14
+ * Add {preference}_changed?, {preference}_was, {preference}_changed, {preference}_will_change!, and reset_{preference}!
15
+ * Add #preferences_changed?, #preferences_changed, and #preference_changes
16
+ * Fix preferences that are reverted externally still getting stored
17
+ * Fix preference definition types not being used to typecast values
18
+ * No longer allow both group and non-group preferences to be looked up at once (except for named scopes)
19
+ * Add support for using Symbols to reference groups
20
+ * Fix #reload not causing unsaved preferences to get reset
21
+ * Raise exception if unknown preference is accessed
22
+ * Rename #set_preference to #write_preference
23
+ * Add caching of preference lookups
24
+ * Fix preferences being stored even if they didn't change
25
+ * Release gems via rake-gemcutter instead of rubyforge
26
+ * Add a generator for db migration to make installation a bit easier [Tim Lowrimore]
27
+ * Add named scopes: #with_preferences and #without_preferences
28
+
29
+ == 0.3.1 / 2009-04-25
30
+
31
+ * Rename Preference#attribute to #name to avoid conflicts with reserved methods in ActiveRecord
32
+
33
+ == 0.3.0 / 2009-04-13
34
+
35
+ * Add dependency on Rails 2.3
36
+ * Remove dependency on plugins_plus
37
+
38
+ == 0.2.0 / 2008-12-14
39
+
40
+ * Remove the PluginAWeek namespace
41
+
42
+ == 0.1.5 / 2008-11-16
43
+
44
+ * Add all prefers/preferred accessors for preferences to be analogous to ActiveRecord column accessors
45
+ * Fix preferences defined in STI subclasses not working [Quinn Shanahan]
46
+
47
+ == 0.1.4 / 2008-10-26
48
+
49
+ * Change how the base module is included to prevent namespacing conflicts
50
+
51
+ == 0.1.3 / 2008-06-29
52
+
53
+ * Add +prefs+ as an alias for +preferences+
54
+ * Fix +preferences+ not properly selecting preferences when a group is specified
55
+ * Improve test coverage
56
+
57
+ == 0.1.2 / 2008-06-22
58
+
59
+ * Remove log files from gems
60
+
61
+ == 0.1.1 / 2008-06-20
62
+
63
+ * Rename preference_values hash to preferences
64
+ * Rename preferences association to stored_preferences
65
+
66
+ == 0.1.0 / 2008-06-19
67
+
68
+ * Avoid string evaluation for dynamic methods
69
+ * Return hashes for the preference_values, e.g.
70
+
71
+ user.preference_values # => {'color' => 'red', 'number' => 11, 'website' => {'background' => 'white', 'foreground' => 'black'}}
72
+ user.preference_values('website') # => {'background' => 'white', 'foreground' => 'black'}
73
+
74
+ * Add more generic grouping of preferences than with just other records, e.g.
75
+
76
+ user.preferred_color('cars')
77
+
78
+ * Remove support for an options hash when specifying :for associations for preference
79
+
80
+ == 0.0.1 / 2008-05-10
81
+
82
+ * 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.
data/README.rdoc ADDED
@@ -0,0 +1,216 @@
1
+ == preferences
2
+
3
+ +preferences+ adds support for easily creating custom preferences for models.
4
+
5
+ == Resources
6
+
7
+ API
8
+
9
+ * http://rdoc.info/projects/pluginaweek/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
+ === Installation
44
+
45
+ +preferences+ requires an additional database table to work. You can generate
46
+ a migration for this table like so:
47
+
48
+ script/generate preferences
49
+
50
+ Then simply migrate your database:
51
+
52
+ rake db:migrate
53
+
54
+ === Defining preferences
55
+
56
+ To define the preferences for a model, you can do so right within the model:
57
+
58
+ class User < ActiveRecord::Base
59
+ preference :hot_salsa
60
+ preference :dark_chocolate, :default => true
61
+ preference :color, :string
62
+ preference :favorite_number
63
+ preference :language, :string, :default => 'English', :group_defaults => {:chat => 'Spanish'}
64
+ end
65
+
66
+ In the above model, 5 preferences have been defined:
67
+ * hot_salsa
68
+ * dark_chocolate
69
+ * color
70
+ * favorite_number
71
+ * language
72
+
73
+ For each preference, a data type and default value can be specified. If no
74
+ data type is given, it's assumed to be a boolean value. If no default value is
75
+ given, the default is assumed to be nil.
76
+
77
+ === Accessing preferences
78
+
79
+ Once preferences have been defined for a model, they can be accessed either
80
+ using the accessor methods that are generated for each preference or the generic
81
+ methods that are not specific to a particular preference.
82
+
83
+ ==== Accessors
84
+
85
+ There are several shortcut methods that are generated for each preference
86
+ defined on a model. These reflect the same set of methods (attribute accessors)
87
+ that are generated for a model's columns. Examples of these are shown below:
88
+
89
+ Query methods:
90
+ user.prefers_hot_salsa? # => false
91
+ user.preferred_language? # => true
92
+
93
+ Reader methods:
94
+ user.prefers_hot_salsa # => false
95
+ user.preferred_language # => "English"
96
+
97
+ Writer methods:
98
+ user.prefers_hot_salsa = false # => false
99
+ user.preferred_language = 'English' # => "English"
100
+
101
+ ==== Generic methods
102
+
103
+ Each preference accessor is essentially a wrapper for the various generic methods
104
+ shown below:
105
+
106
+ Query method:
107
+ user.prefers?(:hot_salsa) # => false
108
+ user.preferred?(:language) # => true
109
+
110
+ Reader method:
111
+ user.prefers(:hot_salsa) # => false
112
+ user.preferred(:language) # => "English"
113
+
114
+ Write method:
115
+ user.write_preference(:hot_salsa, false) # => false
116
+ user.write_preference(:language, "English") # => "English"
117
+
118
+ === Accessing all preferences
119
+
120
+ To get the collection of all custom, stored preferences for a particular record,
121
+ you can access the +stored_preferences+ has_many association which is automatically
122
+ generated:
123
+
124
+ user.stored_preferences
125
+
126
+ In addition to this, you can get a hash of all stored preferences *and* default
127
+ preferences, by accessing the +preferences+ helper:
128
+
129
+ user.preferences # => {"language"=>"English", "color"=>nil}
130
+
131
+ This hash will contain the value for every preference that has been defined for
132
+ the model, whether that's the default value or one that has been previously
133
+ stored.
134
+
135
+ A short-hand alternative for preferences is also available:
136
+
137
+ user.prefs # => {"language"=>"English", "color"=>nil}
138
+
139
+ === Grouping preferences
140
+
141
+ In addition to defining generic preferences for the owning record, you can also
142
+ group preferences by ActiveRecord objects or arbitrary names. This is best shown
143
+ through an example:
144
+
145
+ user = User.find(:first)
146
+ car = Car.find(:first)
147
+
148
+ user.preferred_color = 'red', car
149
+ # user.write_preference(:color, 'red', car) # The generic way
150
+
151
+ This will create a color preference of "red" for the given car. In this way,
152
+ you can have "color" preferences for different records.
153
+
154
+ To access the preference for a particular record, you can use the same accessor
155
+ methods as before:
156
+
157
+ user.preferred_color(car)
158
+ # user.preferred(:color, car) # The generic way
159
+
160
+ In addition to grouping preferences for a particular record, you can also group
161
+ preferences by name. For example,
162
+
163
+ user = User.find(:first)
164
+
165
+ user.preferred_color = 'red', :automobiles
166
+ user.preferred_color = 'tan', :clothing
167
+
168
+ user.preferred_color(:automobiles) # => "red"
169
+ user.preferred_color(:clothing) # => "tan"
170
+
171
+ user.preferences(:automobiles) # => {"color"=>"red"}
172
+
173
+ === Saving preferences
174
+
175
+ Note that preferences are not saved until the owning record is saved.
176
+ Preferences are treated in a similar fashion to attributes. For example,
177
+
178
+ user = user.find(:first)
179
+ user.attributes = {:prefers_hot_salsa => false, :preferred_color => 'red'}
180
+ user.save!
181
+
182
+ Preferences are stored in a separate table called "preferences".
183
+
184
+ === Tracking changes
185
+
186
+ Similar to ActiveRecord attributes, unsaved changes to preferences can be
187
+ tracked. For example,
188
+
189
+ user.preferred_language # => "English"
190
+ user.preferred_language_changed? # => false
191
+ user.preferred_language = 'Spanish'
192
+ user.preferred_language_changed? # => true
193
+ user.preferred_language_was # => "English"
194
+ user.preferred_language_change # => ["English", "Spanish"]
195
+ user.reset_preferred_language!
196
+ user.preferred_language # => "English"
197
+
198
+ Assigning the same value leaves the preference unchanged:
199
+
200
+ user.preferred_language # => "English"
201
+ user.preferred_language = 'English'
202
+ user.preferred_language_changed? # => false
203
+ user.preferred_language_change # => nil
204
+
205
+ == Testing
206
+
207
+ Before you can run any tests, the following gem must be installed:
208
+ * plugin_test_helper[http://github.com/pluginaweek/plugin_test_helper]
209
+
210
+ To run against a specific version of Rails:
211
+
212
+ rake test RAILS_FRAMEWORK_ROOT=/path/to/rails
213
+
214
+ == Dependencies
215
+
216
+ * Rails 2.3 or later
data/Rakefile ADDED
@@ -0,0 +1,75 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+
7
+ spec = Gem::Specification.new do |s|
8
+ s.name = 'preferences'
9
+ s.version = '0.4.2'
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = 'Adds support for easily creating custom preferences for ActiveRecord models'
12
+ s.description = s.summary
13
+
14
+ s.files = FileList['{app,generators,lib,test}/**/*'] + %w(CHANGELOG.rdoc init.rb LICENSE Rakefile README.rdoc) - FileList['test/app_root/{log,log/*,script,script/*}']
15
+ s.require_path = 'lib'
16
+ s.has_rdoc = true
17
+ s.test_files = Dir['test/**/*_test.rb']
18
+
19
+ s.author = 'Aaron Pfeifer'
20
+ s.email = 'aaron@pluginaweek.org'
21
+ s.homepage = 'http://www.pluginaweek.org'
22
+ s.rubyforge_project = 'pluginaweek'
23
+ end
24
+
25
+ desc 'Default: run all tests.'
26
+ task :default => :test
27
+
28
+ desc "Test the #{spec.name} plugin."
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.test_files = spec.test_files
32
+ t.verbose = true
33
+ end
34
+
35
+ begin
36
+ require 'rcov/rcovtask'
37
+ namespace :test do
38
+ desc "Test the #{spec.name} plugin with Rcov."
39
+ Rcov::RcovTask.new(:rcov) do |t|
40
+ t.libs << 'lib'
41
+ t.test_files = spec.test_files
42
+ t.rcov_opts << '--exclude="^(?!lib/|app/)"'
43
+ t.verbose = true
44
+ end
45
+ end
46
+ rescue LoadError
47
+ end
48
+
49
+ desc "Generate documentation for the #{spec.name} plugin."
50
+ Rake::RDocTask.new(:rdoc) do |rdoc|
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = spec.name
53
+ rdoc.template = '../rdoc_template.rb'
54
+ rdoc.options << '--line-numbers' << '--inline-source'
55
+ rdoc.rdoc_files.include('README.rdoc', 'CHANGELOG.rdoc', 'LICENSE', 'lib/**/*.rb', 'app/**/*.rb')
56
+ end
57
+
58
+ desc 'Generate a gemspec file.'
59
+ task :gemspec do
60
+ File.open("#{spec.name}.gemspec", 'w') do |f|
61
+ f.write spec.to_ruby
62
+ end
63
+ end
64
+
65
+ Rake::GemPackageTask.new(spec) do |p|
66
+ p.gem_spec = spec
67
+ end
68
+
69
+ desc 'Publish the release files to RubyForge.'
70
+ task :release => :package do
71
+ require 'rake/gemcutter'
72
+
73
+ Rake::Gemcutter::Tasks.new(spec)
74
+ Rake::Task['gem:push'].invoke
75
+ 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.is_a?(Symbol) ? group.to_s : 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