hobo 0.8.8 → 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. data/CHANGES.txt +34 -0
  2. data/Rakefile +30 -24
  3. data/bin/hobo +30 -10
  4. data/doctest/hobo/hobo_helper.rdoctest +92 -0
  5. data/doctest/hobo/lifecycles.rdoctest +261 -0
  6. data/doctest/scopes.rdoctest +387 -0
  7. data/dryml_generators/rapid/forms.dryml.erb +3 -3
  8. data/dryml_generators/rapid/pages.dryml.erb +4 -4
  9. data/lib/active_record/viewhints_validations_interceptor.rb +1 -1
  10. data/lib/hobo.rb +1 -1
  11. data/lib/hobo/accessible_associations.rb +3 -3
  12. data/lib/hobo/authentication_support.rb +1 -1
  13. data/lib/hobo/dryml.rb +10 -0
  14. data/lib/hobo/dryml/taglib.rb +3 -5
  15. data/lib/hobo/hobo_helper.rb +3 -1
  16. data/lib/hobo/include_in_save.rb +1 -0
  17. data/lib/hobo/lifecycles/actions.rb +6 -2
  18. data/lib/hobo/model.rb +1 -1
  19. data/lib/hobo/model_controller.rb +34 -12
  20. data/lib/hobo/permissions.rb +1 -1
  21. data/lib/hobo/rapid_helper.rb +3 -0
  22. data/lib/hobo/scopes/association_proxy_extensions.rb +8 -2
  23. data/lib/hobo/scopes/automatic_scopes.rb +3 -3
  24. data/lib/hobo/user_controller.rb +2 -1
  25. data/rails_generators/hobo/hobo_generator.rb +1 -1
  26. data/rails_generators/hobo/templates/application.dryml +0 -2
  27. data/rails_generators/hobo_admin_site/hobo_admin_site_generator.rb +45 -0
  28. data/rails_generators/hobo_admin_site/templates/admin.css +2 -0
  29. data/rails_generators/hobo_admin_site/templates/application.dryml +1 -0
  30. data/rails_generators/hobo_admin_site/templates/controller.rb +13 -0
  31. data/rails_generators/hobo_admin_site/templates/site_taglib.dryml +32 -0
  32. data/rails_generators/hobo_admin_site/templates/users_index.dryml +5 -0
  33. data/rails_generators/hobo_front_controller/hobo_front_controller_generator.rb +7 -1
  34. data/rails_generators/hobo_front_controller/templates/index.dryml +16 -0
  35. data/rails_generators/hobo_rapid/hobo_rapid_generator.rb +31 -1
  36. data/rails_generators/hobo_rapid/templates/hobo-rapid.js +5 -3
  37. data/rails_generators/hobo_rapid/templates/lowpro.js +40 -21
  38. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/101-3B5F87-ACD3E6.png +0 -0
  39. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-3E547A-242E42.png +0 -0
  40. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/30-DBE1E5-FCFEF5.png +0 -0
  41. data/rails_generators/hobo_rapid/templates/themes/clean/public/stylesheets/clean.css +12 -4
  42. data/rails_generators/hobo_subsite/hobo_subsite_generator.rb +1 -1
  43. data/rails_generators/hobo_user_controller/hobo_user_controller_generator.rb +22 -0
  44. data/rails_generators/hobo_user_controller/templates/accept_invitation.dryml +5 -0
  45. data/rails_generators/hobo_user_controller/templates/controller.rb +22 -0
  46. data/rails_generators/hobo_user_model/hobo_user_model_generator.rb +17 -1
  47. data/rails_generators/hobo_user_model/templates/invite.erb +9 -0
  48. data/rails_generators/hobo_user_model/templates/mailer.rb +15 -0
  49. data/rails_generators/hobo_user_model/templates/model.rb +31 -4
  50. data/taglibs/rapid_core.dryml +25 -6
  51. data/taglibs/rapid_forms.dryml +65 -24
  52. data/taglibs/rapid_lifecycles.dryml +1 -1
  53. data/taglibs/rapid_navigation.dryml +2 -2
  54. data/taglibs/rapid_plus.dryml +4 -3
  55. metadata +151 -210
  56. data/Manifest +0 -155
  57. data/hobo.gemspec +0 -46
  58. data/rails_generators/hobo_rapid/templates/themes/clean/public/images/100-3B5F87-ACD3E6.png +0 -0
@@ -0,0 +1,387 @@
1
+ Hobo Scopes
2
+ {: .document-title}
3
+
4
+ Hobo scopes are an extension of the *named scope* and *dynamic finder*
5
+ functionality introduced in Rails 2.1, 2.2 and 2.3.
6
+
7
+ Most of these scopes work by calling `named_scope` the first time they
8
+ are invoked. They should work at the same speed as a named scope on
9
+ subsequent invocations.
10
+
11
+ However, this does substantially slow down `method_missing` on your
12
+ model's class. If `ActiveRecord::Base.method_missing` is used often,
13
+ you may wish to disable this module. (FIXME: how to do that)
14
+
15
+ Contents
16
+ {: .contents-heading}
17
+
18
+ - contents
19
+ {:toc}
20
+
21
+ This document was created using the tool
22
+ [rubydoctest](http://github.com/tablatom/rubydoctest). This means
23
+ that this file serves as both documentation and test. As a side
24
+ effect, it also ensures that errors do not creep into the sample code
25
+ in this documentation.
26
+ {.hidden}
27
+
28
+ The idea behind rubydoctest is that you should be able to recreate
29
+ everything in this document from an irb console. It is recommended
30
+ that you skip down to the [fixture definitions](#fixture_definition).
31
+ Nobody but the computer needs to read the rest of this section.
32
+ {.hidden}
33
+
34
+ To test Hobo scopes, we're going to need a connection to the database
35
+ and some sample data. This takes a few lines of code to set up when
36
+ starting from a blank irb session.
37
+ {.hidden}
38
+
39
+ First off we need to configure ActiveSupport for auto-loading
40
+ {.hidden}
41
+
42
+ >> require 'rubygems'
43
+ >> require 'activesupport'
44
+ >> require 'activerecord'
45
+ >> require 'actionpack'
46
+ >> require 'action_view'
47
+ >> require 'action_controller'
48
+ {.hidden}
49
+
50
+ We also need to get ActiveRecord set up with a database connection
51
+ {.hidden}
52
+
53
+ >> mysql_database = "hobofields_doctest"
54
+ >> system "mysqladmin --force drop #{mysql_database} 2> /dev/null"
55
+ >> system("mysqladmin create #{mysql_database}") or raise "could not create database"
56
+ >> ActiveRecord::Base.establish_connection(:adapter => "mysql",
57
+ :database => mysql_database,
58
+ :host => "localhost")
59
+ {.hidden}
60
+
61
+ Some load path manipulation:
62
+ {.hidden}
63
+
64
+ >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
65
+ >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
66
+ >> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobo/lib')
67
+ {.hidden}
68
+
69
+ And we'll require hobo:
70
+ {.hidden}
71
+
72
+ >> require 'hobosupport'
73
+ >> require 'hobofields'
74
+ >> require 'hobo'
75
+ >> Hobo::Model.enable
76
+ {.hidden}
77
+
78
+ Let's set up a few models for our testing:
79
+
80
+ >>
81
+ class Person < ActiveRecord::Base
82
+ hobo_model
83
+
84
+ fields do
85
+ name :string
86
+ born_at :date
87
+ code :integer
88
+ male :boolean
89
+ timestamps
90
+ end
91
+
92
+ lifecycle(:key_timestamp_field => false) do
93
+ state :inactive, :active
94
+ end
95
+
96
+ has_many :friendships
97
+ has_many :friends, :through => :friendships
98
+ end
99
+
100
+ >>
101
+ class Friendship < ActiveRecord::Base
102
+ hobo_model
103
+ belongs_to :person
104
+ belongs_to :friend, :class_name => "Person"
105
+ end
106
+
107
+ Generate a migration and run it:
108
+ {.hidden}
109
+
110
+ >> ActiveRecord::Migration.class_eval(HoboFields::MigrationGenerator.run[0])
111
+ >> Person.columns.*.name
112
+ => ["id", "name", "born_at", "code", "male", "created_at", "updated_at", "state"]
113
+ {.hidden}
114
+
115
+ And create a couple of fixtures:
116
+
117
+ >>
118
+ Bryan = Person.new(:name => "Bryan", :code => 17,
119
+ :born_at => Date.new(1973,4,8), :male => true)
120
+ >> Bryan.state = "active"
121
+ >> Bryan.save!
122
+ >>
123
+ Bethany = Person.new(:name => "Bethany", :code => 42,
124
+ :born_at => Date.new(1975,5,13), :male => false)
125
+ >> Bethany.state = "inactive"
126
+ >> Bethany.save!
127
+ >> Friendship.new(:person => Bryan, :friend => Bethany).save!
128
+
129
+ Hack the `created_at` column to get predictable sorting.
130
+
131
+ >> Bethany.created_at = Date.new(2000)
132
+ >> Bethany.save!
133
+
134
+ We're ready to get going.
135
+
136
+ # Simple Scopes
137
+
138
+ ## \_is
139
+
140
+ Most Hobo scopes work by appending an appropriate query string to the
141
+ field name. In this case, the hobo scope function name is the name of
142
+ your database column, followed by `_is`. It returns an Array of models.
143
+
144
+ It works the same as a dynamic finder:
145
+
146
+ >> Person.find_all_by_name("Bryan").*.name
147
+ => ["Bryan"]
148
+ >> Person.name_is("Bryan").*.name
149
+ => ["Bryan"]
150
+ >> Person.code_is(17).*.name
151
+ => ["Bryan"]
152
+ >> Person.code_is(99).length
153
+ => 0
154
+
155
+ ## \_is\_not
156
+
157
+ But the Hobo scope form allows us to supply several variations
158
+
159
+ >> Person.name_is_not("Bryan").*.name
160
+ => ["Bethany"]
161
+
162
+ ## \_contains
163
+
164
+ >> Person.name_contains("y").*.name
165
+ => ["Bryan", "Bethany"]
166
+
167
+ ## \_does\_not\_contain
168
+
169
+ >> Person.name_does_not_contain("B").*.name
170
+ => []
171
+
172
+ ## \_starts
173
+
174
+ >> Person.name_starts("B").*.name
175
+ => ["Bryan", "Bethany"]
176
+
177
+ ## \_does\_not\_start
178
+
179
+ >> Person.name_does_not_start("B").length
180
+ => 0
181
+
182
+ ## \_ends
183
+
184
+ >> Person.name_ends("y").*.name
185
+ => ["Bethany"]
186
+
187
+ ## \_does\_not\_end
188
+
189
+ >> Person.name_does_not_end("y").*.name
190
+ => ["Bryan"]
191
+
192
+ # Boolean scopes
193
+
194
+ ## \_
195
+
196
+ If you use the name of the column by itself, the column is of type
197
+ boolean, and no function is already defined on the model class with
198
+ the name, Hobo scopes adds a dynamic finder to return all records with
199
+ the boolean column set to `true`
200
+
201
+ >> Person.male.*.name
202
+ => ["Bryan"]
203
+
204
+ ## not\_
205
+
206
+ You can also search for boolean records that are not `true`. This
207
+ includes all records that are set to `false` or `NULL`.
208
+
209
+ >> Person.not_male.*.name
210
+ => ["Bethany"]
211
+
212
+ # Date scopes
213
+
214
+ Date scopes work only with columns that have a name ending in "_at".
215
+ The "_at" is omitted when using these finders.
216
+
217
+ ## \_before
218
+
219
+ >> Person.born_before(Date.new(1974)).*.name
220
+ => ["Bryan"]
221
+
222
+ ## \_after
223
+
224
+ >> Person.born_after(Date.new(1974)).*.name
225
+ => ["Bethany"]
226
+
227
+ ## \_between
228
+
229
+ >> Person.born_between(Date.new(1974), Date.today).*.name
230
+ => ["Bethany"]
231
+
232
+ # Lifecycle scopes
233
+
234
+ If you have a [lifecycle](/manual/lifecycles) defined, each state name
235
+ can be used as a dynamic finder.
236
+
237
+ >> Person.active.*.name
238
+ => ["Bryan"]
239
+
240
+ # Key scopes
241
+
242
+ This isn't very useful:
243
+
244
+ >> Person.is(Bryan).*.name
245
+ => ["Bryan"]
246
+
247
+ But this is:
248
+
249
+ >> Person.is_not(Bryan).*.name
250
+ => ["Bethany"]
251
+
252
+ # Static scopes
253
+
254
+ These scopes do not contain the column name.
255
+
256
+ ## by\_most\_recent
257
+
258
+ Sorting on the `created_at` column:
259
+
260
+ >> Person.by_most_recent.*.name
261
+ => ["Bryan", "Bethany"]
262
+
263
+ ## recent
264
+
265
+ Gives the N most recent items:
266
+
267
+ >> Person.recent(1).*.name
268
+ => ["Bryan"]
269
+
270
+ ## limit
271
+
272
+ >> Person.limit(1).*.name
273
+ => ["Bryan"]
274
+
275
+ ## order\_by
276
+
277
+ >> Person.order_by(:code).*.name
278
+ => ["Bryan", "Bethany"]
279
+
280
+ ## include
281
+
282
+ Adding the include function to your query chain has the same effect as
283
+ the `:include` option to the `find` method.
284
+
285
+ >> Person.include(:friends).*.name
286
+ => ["Bryan", "Bethany"]
287
+
288
+ ## search
289
+
290
+ Search for text in the specified column(s).
291
+
292
+ >> Person.search("B", :name).*.name
293
+ => ["Bryan", "Bethany"]
294
+
295
+ # Association Scopes
296
+
297
+ ## with\_
298
+
299
+ Find the records that contain the specified record in an association
300
+
301
+ >> Person.with_friendship(Friendship.first).*.name
302
+ => ["Bryan"]
303
+ >> Person.with_friend(Bethany).*.name
304
+ => ["Bryan"]
305
+
306
+ You can also specify multiple records with the plural form
307
+
308
+ >> Person.with_friends(Bethany, nil).*.name
309
+ => ["Bryan"]
310
+
311
+ ## without\_
312
+
313
+ >> Person.without_friend(Bethany).*.name
314
+ => ["Bethany"]
315
+ >> Person.without_friends(Bethany, nil).*.name
316
+ => ["Bethany"]
317
+
318
+
319
+ ## \_is
320
+
321
+ You can use \_is on a `:has_one` or a `:belongs_to` relationship:
322
+
323
+ >> Friendship.person_is(Bryan).*.friend.*.name
324
+ => ["Bethany"]
325
+
326
+ ## \_is\_not
327
+
328
+ >> Friendship.person_is_not(Bryan)
329
+ => []
330
+
331
+ # Scoping Associations
332
+
333
+ When defining an association, you can add a scope:
334
+
335
+ >>
336
+ class Person
337
+ has_many :active_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => :active
338
+ has_many :inactive_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => :inactive
339
+ end
340
+
341
+ >> Bryan.inactive_friends.*.name
342
+ => ["Bethany"]
343
+ >> Bryan.active_friends.*.name
344
+ => []
345
+
346
+ or several scopes:
347
+
348
+ >>
349
+ class Person
350
+ has_many :inactive_female_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:inactive, :not_male]
351
+ has_many :active_female_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:active, :not_male]
352
+ has_many :inactive_male_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => [:inactive, :male]
353
+ end
354
+
355
+ >> Bryan.inactive_female_friends.*.name
356
+ => ["Bethany"]
357
+ >> Bryan.active_female_friends.*.name
358
+ => []
359
+ >> Bryan.inactive_male_friends.*.name
360
+ => []
361
+
362
+ You can parameterize the scopes:
363
+
364
+ >>
365
+ class Person
366
+ has_many :y_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => { :name_contains => 'y' }
367
+ has_many :z_friends, :class_name => "Person", :through => :friendships, :source => :friend, :scope => { :name_contains => 'z' }
368
+ end
369
+
370
+ >> Bryan.y_friends.*.name
371
+ => ["Bethany"]
372
+ >> Bryan.z_friends.*.name
373
+ => []
374
+
375
+
376
+ # Chaining
377
+
378
+ Like named scopes, Hobo scopes can be chained:
379
+
380
+ >> Bryan.inactive_friends.inactive.*.name
381
+ => ["Bethany"]
382
+
383
+ Clean up our test database:
384
+ {.hidden}
385
+
386
+ >> system "mysqladmin --force drop #{mysql_database} 2> /dev/null"
387
+ {.hidden}
@@ -18,7 +18,7 @@ cancel_to_index_page = !cancel_to_index_page && linkable?(:index)
18
18
  <% creators.each do |creator| -%>
19
19
  <def tag="<%= creator.name.to_s.dasherize %>-form" polymorphic/>
20
20
  <def tag="<%= creator.name.to_s.dasherize %>-form" for="<%= model.name %>">
21
- <form lifecycle="<%= creator.name %>">
21
+ <form lifecycle="<%= creator.name %>" merge param="default">
22
22
  <error-messages param/>
23
23
  <field-list fields="<%= creator.parameters * ', ' %>" param/>
24
24
  <div param="actions">
@@ -31,7 +31,7 @@ cancel_to_index_page = !cancel_to_index_page && linkable?(:index)
31
31
  <% transitions.each do |transition| -%>
32
32
  <def tag="<%= transition.name.to_s.dasherize %>-form" polymorphic/>
33
33
  <def tag="<%= transition.name.to_s.dasherize %>-form" for="<%= model.name %>">
34
- <form lifecycle="<%= transition.name %>">
34
+ <form lifecycle="<%= transition.name %>" merge param="default">
35
35
  <error-messages param/>
36
36
  <input type="hidden" name="key" value="&this.lifecycle.provided_key" if="&this.lifecycle.provided_key"/>
37
37
  <field-list fields="<%= transition.parameters * ', ' %>" param/>
@@ -42,4 +42,4 @@ cancel_to_index_page = !cancel_to_index_page && linkable?(:index)
42
42
  </def>
43
43
  <% end -%>
44
44
 
45
- <% end # of each_model do -%>
45
+ <% end # of each_model do -%>
@@ -55,7 +55,7 @@ new_form = !new_link && linkable?(model, :create, :method => :post)
55
55
  <% if new_form -%>
56
56
  <div param="new-form">
57
57
  <h3 param="new-form-heading">New <%= model_name :title %></h3>
58
- <form with="&new_for_current_user <%= model %>" param/>
58
+ <form with="&@invalid_record || new_for_current_user(<%= model %>)" param/>
59
59
  </div>
60
60
 
61
61
  <% end -%>
@@ -188,8 +188,8 @@ end
188
188
  </preview-with-more:<%= refl.name %>.recent>
189
189
  <% else -%>
190
190
  <section param="<%= refl.name %>-collection-section">
191
- <h3 param="<%= refl.name %>-collection"><%= refl.name.to_s.titleize %></h3>
192
- <collection:<%= refl.name %>/>
191
+ <h3 param="<%= refl.name %>-collection-heading"><%= refl.name.to_s.titleize %></h3>
192
+ <collection:<%= refl.name %> param="<%= refl.name %>-collection"/>
193
193
  </section>
194
194
  <% end -%>
195
195
  <% end -%>
@@ -250,7 +250,7 @@ new_link = :new.in?(actions)
250
250
  <div param="back-to">Back to <a with="&@<%= owner %>"/></div>
251
251
  <% end -%>
252
252
  <% if owner_is_user %>
253
- <h2 param="heading"><Your with="&current_user"/> <%= model_name :title, :plural %></h2>
253
+ <h2 param="heading"><Your with="&@<%= owner %>"/> <%= model_name :title, :plural %></h2>
254
254
  <% else -%>
255
255
  <h2 param="heading"><%= model_name :title, :plural %></h2>
256
256
  <h3 param="subheading">For: <<%= owner_tag %> with="&@<%= owner %>"/></h3>
@@ -2,7 +2,7 @@ module Hobo
2
2
  module ViewHintsValidationsInterceptor
3
3
  def human_attribute_name(attribute_key_name, opt={})
4
4
  view_hints_field_names = self.view_hints.field_names
5
- view_hints_field_names.include?(attribute_key_name.to_sym) ?
5
+ attribute_key_name!="" && view_hints_field_names.include?(attribute_key_name.to_sym) ?
6
6
  view_hints_field_names[attribute_key_name.to_sym] : super
7
7
  end
8
8
  end
data/lib/hobo.rb CHANGED
@@ -16,7 +16,7 @@ class HoboError < RuntimeError; end
16
16
 
17
17
  module Hobo
18
18
 
19
- VERSION = "0.8.8"
19
+ VERSION = "0.8.9"
20
20
 
21
21
  class PermissionDeniedError < RuntimeError; end
22
22