has_easy 0.9.0

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