has_easy 0.9.0

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.
data/CHANGELOG ADDED
@@ -0,0 +1,16 @@
1
+ 06/23/2008
2
+ * added :postprocess option
3
+ * only the "underscore accessors" are affected by :preprocess and :postprocess
4
+
5
+ 06/20/2008
6
+ * added :default_dynamic option
7
+
8
+ 06/19/2008
9
+ * added validation via naming a method with a Symbol
10
+ * added support for custom validation error messages
11
+
12
+ 06/18/2008
13
+ * added :alias and :aliases options to has_easy
14
+
15
+ 03/28/2012
16
+ * converted to a gem, version 0.9.0 (Jeff Wigal)
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "shoulda", ">= 0"
10
+ gem "bundler", "~> 1.0.0"
11
+ gem "jeweler", "~> 1.6.4"
12
+ gem "rcov", ">= 0"
13
+ gem "sqlite3"
14
+ end
15
+
16
+ gem "rails", ">= 3.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,100 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ actionmailer (3.1.3)
5
+ actionpack (= 3.1.3)
6
+ mail (~> 2.3.0)
7
+ actionpack (3.1.3)
8
+ activemodel (= 3.1.3)
9
+ activesupport (= 3.1.3)
10
+ builder (~> 3.0.0)
11
+ erubis (~> 2.7.0)
12
+ i18n (~> 0.6)
13
+ rack (~> 1.3.5)
14
+ rack-cache (~> 1.1)
15
+ rack-mount (~> 0.8.2)
16
+ rack-test (~> 0.6.1)
17
+ sprockets (~> 2.0.3)
18
+ activemodel (3.1.3)
19
+ activesupport (= 3.1.3)
20
+ builder (~> 3.0.0)
21
+ i18n (~> 0.6)
22
+ activerecord (3.1.3)
23
+ activemodel (= 3.1.3)
24
+ activesupport (= 3.1.3)
25
+ arel (~> 2.2.1)
26
+ tzinfo (~> 0.3.29)
27
+ activeresource (3.1.3)
28
+ activemodel (= 3.1.3)
29
+ activesupport (= 3.1.3)
30
+ activesupport (3.1.3)
31
+ multi_json (~> 1.0)
32
+ arel (2.2.3)
33
+ builder (3.0.0)
34
+ erubis (2.7.0)
35
+ git (1.2.5)
36
+ hike (1.2.1)
37
+ i18n (0.6.0)
38
+ jeweler (1.6.4)
39
+ bundler (~> 1.0)
40
+ git (>= 1.2.5)
41
+ rake
42
+ json (1.6.5)
43
+ mail (2.3.2)
44
+ i18n (>= 0.4.0)
45
+ mime-types (~> 1.16)
46
+ treetop (~> 1.4.8)
47
+ mime-types (1.17.2)
48
+ multi_json (1.1.0)
49
+ polyglot (0.3.3)
50
+ rack (1.3.6)
51
+ rack-cache (1.2)
52
+ rack (>= 0.4)
53
+ rack-mount (0.8.3)
54
+ rack (>= 1.0.0)
55
+ rack-ssl (1.3.2)
56
+ rack
57
+ rack-test (0.6.1)
58
+ rack (>= 1.0)
59
+ rails (3.1.3)
60
+ actionmailer (= 3.1.3)
61
+ actionpack (= 3.1.3)
62
+ activerecord (= 3.1.3)
63
+ activeresource (= 3.1.3)
64
+ activesupport (= 3.1.3)
65
+ bundler (~> 1.0)
66
+ railties (= 3.1.3)
67
+ railties (3.1.3)
68
+ actionpack (= 3.1.3)
69
+ activesupport (= 3.1.3)
70
+ rack-ssl (~> 1.3.2)
71
+ rake (>= 0.8.7)
72
+ rdoc (~> 3.4)
73
+ thor (~> 0.14.6)
74
+ rake (0.9.2.2)
75
+ rcov (1.0.0)
76
+ rdoc (3.12)
77
+ json (~> 1.4)
78
+ shoulda (2.11.3)
79
+ sprockets (2.0.3)
80
+ hike (~> 1.2)
81
+ rack (~> 1.0)
82
+ tilt (~> 1.1, != 1.3.0)
83
+ sqlite3 (1.3.5)
84
+ thor (0.14.6)
85
+ tilt (1.3.3)
86
+ treetop (1.4.10)
87
+ polyglot
88
+ polyglot (>= 0.3.1)
89
+ tzinfo (0.3.32)
90
+
91
+ PLATFORMS
92
+ ruby
93
+
94
+ DEPENDENCIES
95
+ bundler (~> 1.0.0)
96
+ jeweler (~> 1.6.4)
97
+ rails (>= 3.0)
98
+ rcov
99
+ shoulda
100
+ sqlite3
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 [name of plugin creator]
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 ADDED
@@ -0,0 +1,306 @@
1
+ Easy access and creation of "has many" relationships.
2
+
3
+ What's the difference between flags, preferences and options? Nothing really, they are just "has many" relationships. So why should I install a separate plugin for each one? This plugin can be used to add preferences, flags, options, etc to any model.
4
+
5
+ ==Installation
6
+ In your Gemfile:
7
+
8
+ gem "has_easy"
9
+
10
+ At the command prompt:
11
+ rails g has_easy_migration
12
+ rake db:migrate
13
+
14
+ ==Example
15
+
16
+ class User < ActiveRecord::Base
17
+ has_easy :preferences do |p|
18
+ p.define :color
19
+ p.define :theme
20
+ end
21
+ has_easy :flags do |f|
22
+ f.define :is_admin
23
+ f.define :is_spammer
24
+ end
25
+ end
26
+
27
+ user = User.new
28
+
29
+ # hash like access
30
+ user.preferences[:color] = 'red'
31
+ user.preferences[:color] # => 'red'
32
+
33
+ # object like access
34
+ user.preferences.theme? # => false, shorthand for !!user.preferences.theme
35
+ user.preferences.theme = "savage thunder"
36
+ user.preferences.theme # => "savage thunder"
37
+ user.preferences.theme? # => true
38
+
39
+ # easy access for form inputs
40
+ user.flags_is_admin? # => false, shorthand for !!user.flags_is_admin
41
+ user.flags_is_admin = true
42
+ user.flags_is_admin # => true
43
+ user.flags_is_admin? # => true
44
+
45
+ # save user's preferences
46
+ user.preferences.save # will trickle down validation errors to user
47
+ user.errors.empty? # hopefully true
48
+
49
+ # save user's flags
50
+ user.flags.save! # will raise exception on validation errors
51
+
52
+
53
+ ==Advanced Usage
54
+ There are a lot of options that you can use with has_easy:
55
+ * aliasing
56
+ * default values
57
+ * inheriting default values from parent associations
58
+ * calculated default values
59
+ * type checking values
60
+ * validating values
61
+ * preprocessing values
62
+ In this section, we'll go over how to use each option and explain why it's useful.
63
+
64
+ ===:alias and :aliases
65
+ These options go on the has_easy method call and specify alternate ways of invoking the association.
66
+ class User < ActiveRecord::Base
67
+ has_easy :preferences, :aliases => [:prefs, :options] do |p|
68
+ p.define :likes_cheese
69
+ end
70
+ has_easy :flags, :alias => :status do |p|
71
+ p.define :is_admin
72
+ end
73
+ end
74
+
75
+ user.preferences.likes_cheese = 'yes'
76
+ user.prefs.likes_cheese => 'yes'
77
+ user.options_likes_cheese => 'yes'
78
+ user.prefs[:likes_cheese] => 'yes'
79
+ user.options.likes_cheese? => true
80
+ ...etc...
81
+
82
+ ===:default
83
+ Very simple. It does what you think it does.
84
+ class User < ActiveRecord::Base
85
+ has_easy :options do |p|
86
+ p.define :gender, :default => 'female'
87
+ end
88
+ end
89
+
90
+ User.new.options.gender # => 'female'
91
+
92
+ ===:default_through
93
+ Allows the model to inherit it's default value from an association.
94
+ class Client < ActiveRecord::Base
95
+ has_many :users
96
+ has_easy :options do |p|
97
+ p.define :gender, :default => 'male'
98
+ end
99
+ end
100
+ class User < ActiveRecord::Base
101
+ belongs_to :client
102
+ has_easy :options do |p|
103
+ p.define :gender, :default_through => :client, :default => 'female'
104
+ end
105
+ end
106
+
107
+ client = Client.create
108
+ user = client.users.create
109
+ user.options.gender # => 'male'
110
+
111
+ client.options.gender = 'asexual'
112
+ client.options.save
113
+ user.client(true) # reload association
114
+ user.options.gender # => 'asexual'
115
+
116
+ User.new.options.gender => 'female'
117
+
118
+
119
+ ===:default_dynamic
120
+ Allows for calculated default values.
121
+ class User < ActiveRecord::Base
122
+ has_easy 'prefs' do |t|
123
+ t.define :likes_cheese, :default_dynamic => :defaults_to_like_cheese
124
+ t.define :is_dumb, :default_dynamic => Proc.new{ |user| user.dumb_post_count > 10 }
125
+ end
126
+
127
+ def defaults_to_like_cheese
128
+ cheesy_post_count > 10
129
+ end
130
+ end
131
+
132
+ user = User.new :cheesy_post_count => 5
133
+ user.prefs.likes_cheese? => false
134
+
135
+ user = User.new :cheesy_post_count => 11
136
+ user.prefs.likes_cheese? => true
137
+
138
+ user = User.new :dumb_post_count => 5
139
+ user.prefs.is_dumb? => false
140
+
141
+ user = User.new :dumb_post_count => 11
142
+ user.prefs.is_dumb? => true
143
+
144
+
145
+ ===:type_check
146
+ Allows type checking of values (for people who are into that).
147
+ class User < ActiveRecord::Base
148
+ has_easy :prefs do |p|
149
+ p.define :theme, :type_check => String
150
+ p.define :dollars, :type_check => [Fixnum, Bignum]
151
+ end
152
+ end
153
+
154
+ user.prefs.theme = 123
155
+ user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like:
156
+ # 'theme' for has_easy('prefs') failed type check
157
+
158
+ user.prefs.dollars = "hello world"
159
+ user.prefs.save
160
+ user.errors.empty? # => false
161
+ user.errors.on(:prefs) # => 'dollars' for has_easy('prefs') failed type check
162
+
163
+
164
+ ===:validate
165
+ Make sure that values fit some kind of criteria. If you use a Proc or name a method with a Symbol to do validation, there are three ways to specify failure:
166
+ 1. return false
167
+ 2. raise a HasEasy::ValidationError
168
+ 3. return an array of custom validation error messages
169
+ class User < ActiveRecord::Base
170
+ has_easy :prefs do |p|
171
+ p.define :foreground, :validate => ['red', 'blue', 'green']
172
+ p.define :background, :validate => Proc.new{ |value| %w[black white grey].include?(value) }
173
+ p.define :midground, :validate => :midground_validator
174
+ end
175
+ def midground_validator(value)
176
+ return ["msg1", msg2] unless %w[yellow brown purple].include?(value)
177
+ end
178
+ end
179
+
180
+ user.prefs.foreground = 'yellow'
181
+ user.prefs.save! # ActiveRecord::InvalidRecord exception raised with message like:
182
+ # 'theme' for has_easy('prefs') failed validation
183
+
184
+ user.prefs.background = "pink"
185
+ user.prefs.save
186
+ user.errors.empty? => false
187
+ user.errors.on(:prefs) => 'background' for has_easy('prefs') failed validation
188
+
189
+ user.prefs.midground = "black"
190
+ user.prefs.save
191
+ user.errors.on(:prefs)[0] => "msg1"
192
+ user.errors.on(:prefs)[1] => "msg2"
193
+
194
+
195
+ ===:preprocess
196
+ Alter the value before it goes through type checking and/or validation. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. <tt>prefs_likes_cheese=</tt>, not <tt>prefs.likes_cheese=</tt> or <tt>prefs[:likes_cheese]=</tt>.
197
+ class User < ActiveRecord::Base
198
+ has_easy :prefs do |p|
199
+ p.define :likes_cheese, :validate => [true, false],
200
+ :preprocess => Proc.new{ |value| ['true', 'yes'].include?(value) ? true : false }
201
+ end
202
+ end
203
+
204
+ user.prefs.likes_cheese = 'yes' # :preprocess NOT invoked; it only applies to underscore accessors!!
205
+ user.prefs.likes_cheese
206
+ => 'yes'
207
+ user.prefs.save! # exception, validation failed
208
+
209
+ user.prefs_likes_cheese = 'yes' # :preprocess invoked
210
+ user.prefs.likes_cheese
211
+ => true
212
+ user.prefs.save! # no exception
213
+
214
+
215
+ ===:postprocess
216
+ Alter the value when it is read. This is useful when working with forms and boolean values. CAREFUL!! This option only applies to the underscore accessors, i.e. <tt>prefs_likes_cheese</tt>, not <tt>prefs.likes_cheese</tt> or <tt>prefs[:likes_cheese]</tt>.
217
+ class User < ActiveRecord::Base
218
+ has_easy :prefs do |p|
219
+ p.define :likes_cheese, :validate => [true, false],
220
+ :postprocess => Proc.new{ |value| value ? 'yes' : 'no' }
221
+ end
222
+ end
223
+
224
+ user.prefs.likes_cheese = true
225
+ user.prefs.likes_cheese # :postprocess NOT invoked, it only applies to underscore accessors
226
+ => true
227
+ user.prefs_likes_cheese # :postprocess invoked
228
+ => 'yes'
229
+
230
+ ==Using with Forms
231
+ Suppose you have a <tt>has_easy</tt> field defined as a boolean and you want to use it with a checkbox in <tt>form_for</tt>.
232
+
233
+ (model)
234
+
235
+ class User < ActiveRecord::Base
236
+ has_easy :prefs do |p|
237
+ p.define :likes_cheese, :type_check => [TrueClass, FalseClass],
238
+ :preprocess => Proc.new{ |value| value == 'yes' },
239
+ :postprocess => Proc.new{ |value| value ? 'yes' : 'no' }
240
+ end
241
+ end
242
+
243
+ (view)
244
+
245
+ <% form_for(@user) do |f| %>
246
+ <%= f.check_box 'user', 'prefs_likes_cheese', {}, 'yes', 'no' %> # invokes @user.prefs_likes_cheese which does the :postprocess
247
+ <% end %>
248
+
249
+ (controller)
250
+
251
+ @user.update_attributes(params[:user]) # invokes @user.prefs_likes_cheese= which does the :preprocess
252
+ @user.prefs.save
253
+ @user.prefs.likes_cheese
254
+ => true or false
255
+ @user.prefs_likes_cheese # remember, only underscore accessors invoke the :preprocess and :postprocess options
256
+ => 'yes' or 'no'
257
+
258
+ The general idea is that we make the form use <tt>prefs_likes_cheese=</tt> and <tt>prefs_likes_cheese</tt> accessors which in turn use the :preprocess and :postprocess options. Then in our normal code, we use <tt>prefs.likes_cheese</tt> or <tt>prefs[:likes_cheese]</tt> accessors to get our expected boolean values.
259
+
260
+ ==Missing Features
261
+
262
+ ===Autovivification
263
+ For when we want to use fields without having to define them first.
264
+ class User < ActiveRecord::Base
265
+ has_easy :prefs, :autovivify => true do |p|
266
+ p.define :likes_cheese, :default => 'yes'
267
+ end
268
+ end
269
+
270
+ user.prefs.likes_cheese => 'yes'
271
+ user.prefs.likes_pizza => nil
272
+ user.prefs.likes_pizza = true
273
+ user.prefs.likes_pizza => true
274
+
275
+
276
+ ===Scoping to other models
277
+ Ehh, can't think of a way to describe this other than example. Also, the syntax is completely up in the air, there are so many different ways to do it, I have no idea which way to go with. Please tell me your ideas.
278
+ class User < ActiveRecord::Base
279
+ has_easy :prefs do |p|
280
+ p.define :subscribed, :scoped => Post
281
+ p.define :color, :scoped => [Car, Motorcycle] # polymorphic but must be Car or Motorcycle
282
+ p.define :hair_color, :scoped => true # polymorphic no restrictions
283
+ p.define :likes_cheese, :scoped => [Food, NilClass] # scoped and not scoped at the same time
284
+ end
285
+ end
286
+
287
+ post = Post.find :first, :conditions => {:topic => 'rails'}
288
+ me.prefs.subscribed? :to => post
289
+ => true
290
+
291
+ vette = Car.find :first, :conditions => {:model => 'corvette'}
292
+ me.prefs.color :for => vette
293
+ => 'black'
294
+
295
+ gf = Girl.find :first, :conditions => {:name => 'aimee'}
296
+ me.prefs.hair_color :on => gf
297
+ => 'brown'
298
+
299
+ watermelon = Food.find :first, :conditions => {:kind => 'watermelon'}
300
+ my.prefs.likes_cheese? # not scoped; do I like cheese in general?
301
+ => true
302
+ my.prefs.likes_cheese? :on => watermelon # scoped; do I like cheese on watermelon?
303
+ => false
304
+
305
+
306
+ Copyright (c) 2008 Christopher J. Bottaro <cjbottaro@alumni.cs.utexas.edu>, released under the MIT license