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 +82 -0
- data/LICENSE +20 -0
- data/README.rdoc +216 -0
- data/Rakefile +75 -0
- data/app/models/preference.rb +65 -0
- data/init.rb +1 -0
- data/lib/generators/preferences/USAGE +5 -0
- data/lib/generators/preferences/preferences_generator.rb +17 -0
- data/lib/generators/preferences/templates/001_create_preferences.rb +16 -0
- data/lib/preferences.rb +619 -0
- data/lib/preferences/engine.rb +8 -0
- data/lib/preferences/preference_definition.rb +56 -0
- data/test/app_root/app/models/car.rb +2 -0
- data/test/app_root/app/models/employee.rb +2 -0
- data/test/app_root/app/models/manager.rb +3 -0
- data/test/app_root/app/models/user.rb +8 -0
- data/test/app_root/db/migrate/001_create_users.rb +11 -0
- data/test/app_root/db/migrate/002_create_cars.rb +11 -0
- data/test/app_root/db/migrate/003_create_employees.rb +12 -0
- data/test/app_root/db/migrate/004_migrate_preferences_to_version_1.rb +13 -0
- data/test/factory.rb +65 -0
- data/test/functional/preferences_test.rb +1364 -0
- data/test/test_helper.rb +26 -0
- data/test/unit/preference_definition_test.rb +237 -0
- data/test/unit/preference_test.rb +236 -0
- metadata +83 -0
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
|