hobo 0.8.8 → 0.8.9

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.
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