hobo 0.9.103 → 0.9.104
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/CHANGES.txt +40 -1
- data/Rakefile +5 -3
- data/doctest/model.rdoctest +332 -0
- data/doctest/multi_model_forms.rdoctest +273 -0
- data/doctest/scopes.rdoctest +1 -0
- data/dryml_generators/rapid/cards.dryml.erb +1 -1
- data/dryml_generators/rapid/pages.dryml.erb +11 -11
- data/lib/active_record/association_collection.rb +10 -0
- data/lib/hobo.rb +1 -2
- data/lib/hobo/dryml/dryml_generator.rb +7 -2
- data/lib/hobo/dryml/template_environment.rb +1 -1
- data/lib/hobo/model_controller.rb +2 -0
- data/lib/hobo/user.rb +8 -0
- data/rails/init.rb +10 -0
- data/rails_generators/hobo/templates/initializer.rb +1 -0
- data/rails_generators/hobo_rapid/templates/hobo-rapid.js +30 -12
- data/taglibs/rapid_core.dryml +6 -5
- data/taglibs/rapid_forms.dryml +43 -4
- data/taglibs/rapid_summary.dryml +1 -2
- data/taglibs/rapid_support.dryml +2 -0
- metadata +7 -6
- data/init.rb +0 -2
- data/lib/hobo/fake_initializer.rb +0 -14
data/CHANGES.txt
CHANGED
@@ -14,8 +14,45 @@ likely to cause conflicts, so it is highly recommended that you have
|
|
14
14
|
your code backed up and in a change control system such as git or
|
15
15
|
subversion.
|
16
16
|
|
17
|
-
=== Hobo 0.9.
|
17
|
+
=== Hobo 0.9.104 (AKA 1.0RC3) ===
|
18
|
+
|
19
|
+
[#604](https://hobo.lighthouseapp.com/projects/8324/tickets/604):
|
20
|
+
|
21
|
+
The new input-many introduced in 0.9.103 had issues with >10 elements,
|
22
|
+
several issues running with IE7 and an issue with its javascript
|
23
|
+
callbacks.
|
24
|
+
|
25
|
+
[#537](https://hobo.lighthouseapp.com/projects/8324/tickets/537):
|
26
|
+
|
27
|
+
`x._?.to_s` now returns nil rather than a blank string
|
28
|
+
|
29
|
+
[#592](https://hobo.lighthouseapp.com/projects/8324/tickets/592):
|
30
|
+
|
31
|
+
If you previously had a snippet such as this:
|
32
|
+
|
33
|
+
<table fields="this, date, account.login">
|
34
|
+
<login-view:>
|
35
|
+
...
|
36
|
+
</login-view:>
|
37
|
+
</table>
|
18
38
|
|
39
|
+
You now have to use:
|
40
|
+
|
41
|
+
<table fields="this, date, account.login">
|
42
|
+
<account-login-view:>
|
43
|
+
...
|
44
|
+
</account-login-view:>
|
45
|
+
</table>
|
46
|
+
|
47
|
+
The same change has been applied to `<field-list>`
|
48
|
+
|
49
|
+
[#568](https://hobo.lighthouseapp.com/projects/8324/tickets/568):
|
50
|
+
|
51
|
+
`hobo_index` now supports the `:scope` option
|
52
|
+
|
53
|
+
See also the [git log](http://github.com/tablatom/hobo/commits/v0.9.104)
|
54
|
+
|
55
|
+
=== Hobo 0.9.103 (AKA 1.0.RC2) ===
|
19
56
|
|
20
57
|
### Warning
|
21
58
|
|
@@ -24,6 +61,8 @@ please check out bug
|
|
24
61
|
[#574](https://hobo.lighthouseapp.com/projects/8324/tickets/574-rails-235-b0rks-our-rake-tasks-running-on-edge-hobo)
|
25
62
|
for a workaround you need to apply to your Rakefile.
|
26
63
|
|
64
|
+
NOTE: fixed in 0.9.104
|
65
|
+
|
27
66
|
### Bugs
|
28
67
|
|
29
68
|
This release fixes a couple of serious bugs:
|
data/Rakefile
CHANGED
@@ -10,7 +10,7 @@ $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '/../hobosupport/
|
|
10
10
|
require 'hobo'
|
11
11
|
|
12
12
|
RUBY = File.join(Config::CONFIG['bindir'], Config::CONFIG['ruby_install_name']).sub(/.*\s.*/m, '"\&"')
|
13
|
-
RUBYDOCTEST = ENV['RUBYDOCTEST'] || "#{RUBY}
|
13
|
+
RUBYDOCTEST = ENV['RUBYDOCTEST'] || "#{RUBY} -S rubydoctest"
|
14
14
|
|
15
15
|
desc "Default Task"
|
16
16
|
task :default => [ :test ]
|
@@ -28,8 +28,9 @@ Rake::TestTask.new(:test) { |t|
|
|
28
28
|
namespace "test" do
|
29
29
|
desc "Run the doctests"
|
30
30
|
task :doctest do |t|
|
31
|
+
files=Dir['doctest/*.rdoctest'].map {|f| File.expand_path(f)}.join(' ')
|
31
32
|
# note, tests in doctest/hobo/ are out of date
|
32
|
-
exit(1) if !system("#{RUBYDOCTEST}
|
33
|
+
exit(1) if !system("#{RUBYDOCTEST} #{files}")
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
@@ -61,7 +62,8 @@ Jeweler::Tasks.new do |gemspec|
|
|
61
62
|
gemspec.add_dependency("rails", [">= 2.2.2"])
|
62
63
|
gemspec.add_dependency("will_paginate", [">= 2.3.11"])
|
63
64
|
gemspec.add_dependency("hobosupport", ["= #{Hobo::VERSION}"])
|
64
|
-
gemspec.add_dependency("hobofields", ["= #{Hobo::VERSION}"])
|
65
|
+
gemspec.add_dependency("hobofields", ["= #{Hobo::VERSION}"])
|
66
|
+
gemspec.files.include %w(tasks/environments.rake tasks/hobo_tasks.rake)
|
65
67
|
end
|
66
68
|
Jeweler::GemcutterTasks.new
|
67
69
|
Jeweler::RubyforgeTasks.new do |rubyforge|
|
@@ -0,0 +1,332 @@
|
|
1
|
+
Hobo's Miscellaneous Model Extensions
|
2
|
+
{.document-title}
|
3
|
+
|
4
|
+
This chapter of the Hobo Manual describes Hobo's model extensions,
|
5
|
+
with the exception of [HoboFields](../hobofields) and
|
6
|
+
[Permissions](../permissions),
|
7
|
+
[Accessible Associations](../multi_model_forms) and [Scopes](../scopes) each of
|
8
|
+
which have their own chapters in this manual. This chapter should
|
9
|
+
describe everything else that Hobo provides to your models.
|
10
|
+
|
11
|
+
Contents
|
12
|
+
{.contents-heading}
|
13
|
+
|
14
|
+
- contents
|
15
|
+
{:toc}
|
16
|
+
|
17
|
+
>> require 'rubygems'
|
18
|
+
>> require 'active_support'
|
19
|
+
>> require 'active_record'
|
20
|
+
>> require 'action_pack'
|
21
|
+
>> require 'action_view'
|
22
|
+
>> require 'action_controller'
|
23
|
+
>> mysql_adapter = defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql'
|
24
|
+
>> mysql_user = 'root'; mysql_password = ''
|
25
|
+
>> mysql_login = "-u #{mysql_user} --password='#{mysql_password}'"
|
26
|
+
>> mysql_database = "hobofields_doctest"
|
27
|
+
>> system "mysqladmin #{mysql_login} --force drop #{mysql_database} 2> /dev/null"
|
28
|
+
>> system("mysqladmin #{mysql_login} create #{mysql_database}") or raise "could not create database"
|
29
|
+
>> ActiveRecord::Base.establish_connection(:adapter => mysql_adapter,
|
30
|
+
:database => mysql_database,
|
31
|
+
:host => "localhost",
|
32
|
+
:username => mysql_user,
|
33
|
+
:password => mysql_password)
|
34
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
|
35
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
|
36
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobo/lib')
|
37
|
+
>> require 'will_paginate'
|
38
|
+
>> require 'will_paginate/finder'
|
39
|
+
>> require 'hobosupport'
|
40
|
+
>> require 'hobofields'
|
41
|
+
>> require 'hobo'
|
42
|
+
>> Hobo::Model.enable
|
43
|
+
>> HoboFields.enable
|
44
|
+
>>
|
45
|
+
def migrate(renames={})
|
46
|
+
up, down = HoboFields::MigrationGenerator.run(renames)
|
47
|
+
puts up
|
48
|
+
ActiveRecord::Migration.class_eval(up)
|
49
|
+
ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
|
50
|
+
[up, down]
|
51
|
+
end
|
52
|
+
{.hidden}
|
53
|
+
|
54
|
+
# Special Attributes
|
55
|
+
|
56
|
+
Rails provides one "special" attribute to your model: `primary_key`
|
57
|
+
|
58
|
+
>> class Foo < ActiveRecord::Base; end
|
59
|
+
>> Foo.primary_key
|
60
|
+
=> "id"
|
61
|
+
|
62
|
+
`primary_key` references one of the columns in your table. Rails
|
63
|
+
provides a default, but you can change it.
|
64
|
+
|
65
|
+
In the same fashion, Hobo provides several special attributes that
|
66
|
+
generally correspond to columns in your database table.
|
67
|
+
|
68
|
+
In Hobo, these attributes are specified by passing options to their
|
69
|
+
declaration:
|
70
|
+
|
71
|
+
>> class User < ActiveRecord::Base; end
|
72
|
+
>>
|
73
|
+
class Post < ActiveRecord::Base
|
74
|
+
hobo_model
|
75
|
+
|
76
|
+
fields do
|
77
|
+
title :string, :name => true, :index => true
|
78
|
+
content :text, :primary_content => true
|
79
|
+
end
|
80
|
+
|
81
|
+
belongs_to :poster, :class_name => "User", :creator => true
|
82
|
+
|
83
|
+
set_search_columns :title, :content
|
84
|
+
never_show :poster
|
85
|
+
|
86
|
+
end
|
87
|
+
>> migrate
|
88
|
+
|
89
|
+
In the above example, `name`, `creator` and `primary_content` specify
|
90
|
+
attributes that have meaning to Hobo. `set_search_columns` and
|
91
|
+
`never_show` also allow you to tag columns for Hobo.
|
92
|
+
|
93
|
+
## name
|
94
|
+
|
95
|
+
Many models have a column in their table that names the object. A
|
96
|
+
good example would be the title column in a blog object.
|
97
|
+
|
98
|
+
fields do
|
99
|
+
title :string, :name => true, :index => true
|
100
|
+
end
|
101
|
+
|
102
|
+
>> Post.name_attribute
|
103
|
+
=> :title
|
104
|
+
|
105
|
+
|
106
|
+
Rapid makes extensive use of this column, both directly and
|
107
|
+
indirectly. The `<name>` tag uses it, `<table-plus>` uses it as the
|
108
|
+
default sort column, to give two examples of direct uses.
|
109
|
+
|
110
|
+
Indirectly it is used much more often via the default `to_s` function
|
111
|
+
that Hobo::Model provides.
|
112
|
+
|
113
|
+
>> post = Post.new(:title => "Hello")
|
114
|
+
>> post.to_s
|
115
|
+
=> "Hello"
|
116
|
+
|
117
|
+
Hobo::Model also provides a default `to_param` function to provide
|
118
|
+
human readable URL's:
|
119
|
+
|
120
|
+
>> post.id = 17
|
121
|
+
>> post.to_param
|
122
|
+
=> "17-hello"
|
123
|
+
|
124
|
+
You are of course welcome to provide your own `to_s` and `to_param`
|
125
|
+
functions, but in most cases the Hobo::Model definitions do well.
|
126
|
+
|
127
|
+
Hobo::Model also provides a default finder, `named`:
|
128
|
+
|
129
|
+
>> post.save!
|
130
|
+
>> Post.named("Hello").title
|
131
|
+
=> "Hello"
|
132
|
+
|
133
|
+
If you are going to be using this finder, it is recommended that you
|
134
|
+
also provide an index for your name column:
|
135
|
+
|
136
|
+
fields do
|
137
|
+
title :string, :name => true, :index => true
|
138
|
+
end
|
139
|
+
|
140
|
+
Sometimes a single column does not do a good job of naming the
|
141
|
+
object. In this case, you can provide your own name method instead:
|
142
|
+
|
143
|
+
>>
|
144
|
+
class Person < ActiveRecord::Base
|
145
|
+
hobo_model
|
146
|
+
fields do
|
147
|
+
first_name :string
|
148
|
+
last_name :string
|
149
|
+
end
|
150
|
+
|
151
|
+
def name
|
152
|
+
first_name + ' ' + last_name
|
153
|
+
end
|
154
|
+
end
|
155
|
+
>> migrate
|
156
|
+
>> person = Person.new(:first_name => "John", :last_name => "Brown")
|
157
|
+
>> person.to_s
|
158
|
+
=> "John Brown"
|
159
|
+
|
160
|
+
If you use a composite name, you do lose a couple of features that
|
161
|
+
require direct database access: the `named` finder, and the ability to
|
162
|
+
use it as a sort column without loading the entire table.
|
163
|
+
|
164
|
+
## primary\_content
|
165
|
+
|
166
|
+
`primary_content` works very similarly to `name`, except that it
|
167
|
+
provides the "description" of the row. This is not used in very many
|
168
|
+
places. Currently it is only used in the generated `<card>` and
|
169
|
+
`<show-page>` for your views, but it may be used in more places in the
|
170
|
+
future.
|
171
|
+
|
172
|
+
fields do
|
173
|
+
content :text, :primary_content => true
|
174
|
+
end
|
175
|
+
|
176
|
+
If you do not explicitly set `primary_content`, Hobo::Model will look
|
177
|
+
for a method or attribute named `description`, `body`, `content`,
|
178
|
+
or `profile` and use that.
|
179
|
+
|
180
|
+
>>
|
181
|
+
class Person < ActiveRecord::Base
|
182
|
+
def profile
|
183
|
+
"Boring"
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
>> Person.primary_content_attribute
|
188
|
+
=> "profile"
|
189
|
+
|
190
|
+
## login
|
191
|
+
|
192
|
+
This field is only used in User models. This attribute specifies
|
193
|
+
the field that uniquely identifies a user. Unsurprisingly, it's
|
194
|
+
primary use is for the `login` field on the signup form, but it is
|
195
|
+
used elsewhere.
|
196
|
+
|
197
|
+
>>
|
198
|
+
class User < ActiveRecord::Base
|
199
|
+
hobo_user_model
|
200
|
+
fields do
|
201
|
+
email_address :string, :login => true, :unique => true
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
>> User.login_attribute
|
206
|
+
=> :email_address
|
207
|
+
|
208
|
+
## creator
|
209
|
+
|
210
|
+
If you specify the `creator` option on one of your fields, Hobo will
|
211
|
+
set it to contain the current user when creating the object.
|
212
|
+
|
213
|
+
Normally this is specified on a belongs\_to:
|
214
|
+
|
215
|
+
>>
|
216
|
+
class Post < ActiveRecord::Base
|
217
|
+
belongs_to :poster, :class_name => "User", :creator => true
|
218
|
+
end
|
219
|
+
|
220
|
+
>> Post.creator_attribute
|
221
|
+
=> :poster
|
222
|
+
|
223
|
+
However, it may also be added as an option to a string field, in which
|
224
|
+
case the `login_attribute` is saved to the field:
|
225
|
+
|
226
|
+
>>
|
227
|
+
class Foo2 < ActiveRecord::Base
|
228
|
+
hobo_model
|
229
|
+
fields do
|
230
|
+
creator_login :string, :creator => true
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
>> Foo2.creator_attribute
|
235
|
+
=> :creator_login
|
236
|
+
|
237
|
+
Creator may also be specified via `attr_accessor` if you wish it to
|
238
|
+
be set without being saved to the database:
|
239
|
+
|
240
|
+
>>
|
241
|
+
class Foo3 < ActiveRecord::Base
|
242
|
+
hobo_model
|
243
|
+
attr_accessor :created_by, :creator => true
|
244
|
+
end
|
245
|
+
|
246
|
+
>> Foo3.creator_attribute
|
247
|
+
=> :created_by
|
248
|
+
|
249
|
+
## set\_search\_columns
|
250
|
+
|
251
|
+
Using the `set_search_columns` class function, you may specify which
|
252
|
+
columns are searched by the rapid tags that provide searching
|
253
|
+
capabilities.
|
254
|
+
|
255
|
+
>>
|
256
|
+
class Post < ActiveRecord::Base
|
257
|
+
set_search_columns :title, :content
|
258
|
+
end
|
259
|
+
|
260
|
+
>> Post.search_columns
|
261
|
+
=> ["title", "content"]
|
262
|
+
|
263
|
+
If you do not provide the search columns, Hobo defaults to `%w(name
|
264
|
+
title body description content profile)`.
|
265
|
+
|
266
|
+
## never\_show
|
267
|
+
|
268
|
+
`never_show` columns are not displayed in any views that Rapid
|
269
|
+
creates.
|
270
|
+
|
271
|
+
>>
|
272
|
+
class Post < ActiveRecord::Base
|
273
|
+
never_show :poster
|
274
|
+
end
|
275
|
+
|
276
|
+
>> Post.never_show?(:poster)
|
277
|
+
=> true
|
278
|
+
|
279
|
+
# typed\_id
|
280
|
+
|
281
|
+
>> post.typed_id
|
282
|
+
=> "post:17"
|
283
|
+
|
284
|
+
`typed_id` is a method added to Hobo models that uniquely identifies
|
285
|
+
the model in your database. This is very useful when coupled with
|
286
|
+
`Hobo::Model.find_by_typed_id`:
|
287
|
+
|
288
|
+
>> Hobo::Model.find_by_typed_id("post:17")
|
289
|
+
=> #<Post id: 17, title: "Hello", content: nil, poster_id: nil>
|
290
|
+
|
291
|
+
This is the mechanism that is used to store the current user in the
|
292
|
+
session. It is also used throughout Rapid.
|
293
|
+
|
294
|
+
# set\_default\_order
|
295
|
+
|
296
|
+
set_default_order :name
|
297
|
+
set_default_order "name DESC"
|
298
|
+
|
299
|
+
This sets the :order option on the finder for the class.
|
300
|
+
|
301
|
+
Note that Rails 2.3 has
|
302
|
+
[default\_scope](http://ryandaigle.com/articles/2008/11/18/what-s-new-in-edge-rails-default-scoping).
|
303
|
+
This may be used instead of `set_default_order`, although currently
|
304
|
+
there are many bugs open against `default_scope` in Rails. See [ticket #395](
|
305
|
+
https://hobo.lighthouseapp.com/projects/8324/tickets/395-remove-default_order-once-were-on-rails-23)
|
306
|
+
for more information on this issue.
|
307
|
+
|
308
|
+
# reverse\_reflection
|
309
|
+
|
310
|
+
This is the mechanism that Hobo uses to find a matching association on
|
311
|
+
the other model.
|
312
|
+
|
313
|
+
>>
|
314
|
+
class Post < ActiveRecord::Base
|
315
|
+
belongs_to :poster, :class_name => "User", :creator => true
|
316
|
+
end
|
317
|
+
>>
|
318
|
+
class User < ActiveRecord::Base
|
319
|
+
has_many :posts, :foreign_key => "poster_id"
|
320
|
+
end
|
321
|
+
>> migrate
|
322
|
+
|
323
|
+
>> Post.reverse_reflection(:poster).name
|
324
|
+
=> :posts
|
325
|
+
|
326
|
+
# view\_hints
|
327
|
+
|
328
|
+
This provides a shortcut to the corresponding
|
329
|
+
[ViewHints](../viewhints) object.
|
330
|
+
|
331
|
+
>> Post.view_hints
|
332
|
+
=> PostHints
|
@@ -0,0 +1,273 @@
|
|
1
|
+
Accessible Associations
|
2
|
+
{.document-title}
|
3
|
+
|
4
|
+
This chapter describes Hobo's support for nested models in forms.
|
5
|
+
This is mostly technical background -- beginners should not have to
|
6
|
+
read more than the introduction.
|
7
|
+
|
8
|
+
Contents
|
9
|
+
{.contents-heading}
|
10
|
+
|
11
|
+
- contents
|
12
|
+
{:toc}
|
13
|
+
|
14
|
+
>> require 'rubygems'
|
15
|
+
>> require 'active_support'
|
16
|
+
>> require 'active_record'
|
17
|
+
>> require 'action_pack'
|
18
|
+
>> require 'action_view'
|
19
|
+
>> require 'action_controller'
|
20
|
+
>> mysql_adapter = defined?(JRUBY_VERSION) ? 'jdbcmysql' : 'mysql'
|
21
|
+
>> mysql_user = 'root'; mysql_password = ''
|
22
|
+
>> mysql_login = "-u #{mysql_user} --password='#{mysql_password}'"
|
23
|
+
>> mysql_database = "hobofields_doctest"
|
24
|
+
>> system "mysqladmin #{mysql_login} --force drop #{mysql_database} 2> /dev/null"
|
25
|
+
>> system("mysqladmin #{mysql_login} create #{mysql_database}") or raise "could not create database"
|
26
|
+
>> ActiveRecord::Base.establish_connection(:adapter => mysql_adapter,
|
27
|
+
:database => mysql_database,
|
28
|
+
:host => "localhost",
|
29
|
+
:username => mysql_user,
|
30
|
+
:password => mysql_password)
|
31
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobofields/lib')
|
32
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
|
33
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobo/lib')
|
34
|
+
>> require 'will_paginate'
|
35
|
+
>> require 'will_paginate/finder'
|
36
|
+
>> require 'hobosupport'
|
37
|
+
>> require 'hobofields'
|
38
|
+
>> require 'hobo'
|
39
|
+
>> Hobo::Model.enable
|
40
|
+
>> HoboFields.enable
|
41
|
+
>> ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(STDOUT)
|
42
|
+
>>
|
43
|
+
def migrate(renames={})
|
44
|
+
up, down = HoboFields::MigrationGenerator.run(renames)
|
45
|
+
puts up
|
46
|
+
ActiveRecord::Migration.class_eval(up)
|
47
|
+
ActiveRecord::Base.send(:subclasses).each { |model| model.reset_column_information }
|
48
|
+
[up, down]
|
49
|
+
end
|
50
|
+
{.hidden}
|
51
|
+
|
52
|
+
# Introduction
|
53
|
+
|
54
|
+
Using multi-model forms in Hobo is very straightforward:
|
55
|
+
|
56
|
+
has_many :posts, :accessible => true
|
57
|
+
|
58
|
+
Once you've done that, the default forms that Hobo builds will use the
|
59
|
+
[input-many](/api_tag_defs/input-many) tag.
|
60
|
+
|
61
|
+
`:accessible => true` works for `has_many`, `has_many :through` and
|
62
|
+
`belongs_to`, but does not work for `has_one` or
|
63
|
+
`has_and_belongs_to_many`.
|
64
|
+
|
65
|
+
It's quite common to also add the `:dependent => :destroy` flag to
|
66
|
+
accessible associations. This also used to trigger magic in Hobo, but
|
67
|
+
this additional magic has been removed and replaced with [View
|
68
|
+
Hints](/manual/viewhints). See the [Rails
|
69
|
+
rdoc](http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html)
|
70
|
+
for more information on `:dependent => :destroy`.
|
71
|
+
|
72
|
+
# Model Support
|
73
|
+
|
74
|
+
We'll use rubydoctest to provide our examples for this section. Here
|
75
|
+
are the models:
|
76
|
+
|
77
|
+
>>
|
78
|
+
class Foo < ActiveRecord::Base
|
79
|
+
hobo_model
|
80
|
+
fields { name :string }
|
81
|
+
has_many :bars, :accessible => true
|
82
|
+
end
|
83
|
+
>>
|
84
|
+
class Bar < ActiveRecord::Base
|
85
|
+
hobo_model
|
86
|
+
fields { name :string }
|
87
|
+
belongs_to :foo
|
88
|
+
end
|
89
|
+
>> migrate
|
90
|
+
|
91
|
+
The `:accessible => true` option patches in
|
92
|
+
`Hobo::AccessibleAssociations` to your ActiveRecord model. It
|
93
|
+
modifies the `bars=` writer function to support assigning an array of
|
94
|
+
records, an array of hashes, an array of ids, or an empty string.
|
95
|
+
|
96
|
+
## Assigning an array of records
|
97
|
+
|
98
|
+
The whole array must be assigned -- any records that are not assigned
|
99
|
+
are deleted from your association.
|
100
|
+
|
101
|
+
>> bar1 = Bar.new(:name => "bar1")
|
102
|
+
>> bar2 = Bar.new(:name => "bar2")
|
103
|
+
>> foo = Foo.new(:name => "foo1")
|
104
|
+
>> foo.bars = [bar1, bar2]
|
105
|
+
>> foo.bars.*.name
|
106
|
+
=> ["bar1", "bar2"]
|
107
|
+
>> foo.save!
|
108
|
+
|
109
|
+
>> foo.bars = [bar2]
|
110
|
+
>> foo.bars.*.name
|
111
|
+
=> ["bar2"]
|
112
|
+
>> foo.save!
|
113
|
+
|
114
|
+
>> bar2.foo.name
|
115
|
+
=> "foo1"
|
116
|
+
>> bar1.reload
|
117
|
+
>> bar1.foo
|
118
|
+
=> nil
|
119
|
+
|
120
|
+
If `:dependent => :destroy` had been set on `has_many :bars`, bar1
|
121
|
+
would now be deleted from the database. Since it hasn't, it still
|
122
|
+
exists in the database but has become orphaned.
|
123
|
+
|
124
|
+
## Assigning an array of hashes
|
125
|
+
|
126
|
+
Assigning an array of hashes maps nicely with how Rails deconstructs
|
127
|
+
your URI encoded query string. For example, your form can return
|
128
|
+
|
129
|
+
foo[bars][0][name]=bar1&foo[bars][0][name]=bar2
|
130
|
+
|
131
|
+
which Rails will decode into your params hash as
|
132
|
+
|
133
|
+
>> params = {"foo" => {"bars" => [ {"name" => "bar3"}, {"name" => "bar4"}]}}
|
134
|
+
|
135
|
+
With Hobo's accessible associations, the params hash may be directly
|
136
|
+
assigned.
|
137
|
+
|
138
|
+
>> foo.attributes = params["foo"]
|
139
|
+
>> foo.bars.*.name
|
140
|
+
=> ["bar3", "bar4"]
|
141
|
+
>> foo.save!
|
142
|
+
|
143
|
+
Because these parameters did not include an ID, Hobo created new bar
|
144
|
+
models. If you include an ID, Hobo looks up the existing record in
|
145
|
+
the database and modifies it with the parameters assigned.
|
146
|
+
|
147
|
+
>> params = {"foo" => {"bars" => [ {:name => "bar3_mod", :id => "#{foo.bars[0].id}"}]}}
|
148
|
+
|
149
|
+
>> old_bar3_id = foo.bars[0].id
|
150
|
+
>> foo.attributes = params["foo"]
|
151
|
+
>> foo.save!
|
152
|
+
>> foo.bars.*.name
|
153
|
+
=> ["bar3_mod"]
|
154
|
+
>> foo.bars[0].id == old_bar3_id
|
155
|
+
=> true
|
156
|
+
|
157
|
+
## Assigning an array of IDs
|
158
|
+
|
159
|
+
While [input-many](/api_tag_defs/input-many) returns an array of
|
160
|
+
hashes, [select-many](/api_tag_defs/select-many) returns an array of
|
161
|
+
ids. These ids must have an "@" prepended.
|
162
|
+
|
163
|
+
>> params = {"foo" => {"bars" => ["@#{bar1.id}", "@#{bar2.id}"]}}
|
164
|
+
>> foo.attributes = params["foo"]
|
165
|
+
>> foo.save!
|
166
|
+
>> foo.bars.*.name
|
167
|
+
=> ["bar1", "bar2"]
|
168
|
+
|
169
|
+
## Assigning an empty string
|
170
|
+
|
171
|
+
You can remove all elements from the association by assigning an empty
|
172
|
+
array:
|
173
|
+
|
174
|
+
>> foo.bars = []
|
175
|
+
>> foo.bars
|
176
|
+
=> []
|
177
|
+
|
178
|
+
However, there is no way to format a URI query string to make Rails
|
179
|
+
construct an empty array in its params hash, so Hobo adds a useful
|
180
|
+
shortcut to it's accessible associations:
|
181
|
+
|
182
|
+
>> foo.bars = ""
|
183
|
+
>> foo.bars
|
184
|
+
=> []
|
185
|
+
|
186
|
+
# View Support
|
187
|
+
|
188
|
+
The Rapid tags [input-many](/api_tag_defs/input-many),
|
189
|
+
[input-all](/api_tag_defs/input-all),
|
190
|
+
[select-many](/api_tag_defs/select-many) and
|
191
|
+
[check-many](/api_tag_defs/check-many) all require accessible
|
192
|
+
associations.
|
193
|
+
|
194
|
+
`input-many` may even be used in a nested fashion:
|
195
|
+
|
196
|
+
<form>
|
197
|
+
<field-list:>
|
198
|
+
<foos-view:>
|
199
|
+
<input-many>
|
200
|
+
<field-list:>
|
201
|
+
<bars-view:>
|
202
|
+
<input-many>
|
203
|
+
</input-many>
|
204
|
+
</bars-view:>
|
205
|
+
</field-list:>
|
206
|
+
</input-many>
|
207
|
+
</foos-view:>
|
208
|
+
</field-list:>
|
209
|
+
</form>
|
210
|
+
|
211
|
+
You do not need to use the accessible association tags -- standard
|
212
|
+
inputs acquire the correct `name` for use with accessible associations
|
213
|
+
when called from the appropriate context. Here's an example form that
|
214
|
+
will work with the example given above in *Model Support*
|
215
|
+
|
216
|
+
<form>
|
217
|
+
<field-list:>
|
218
|
+
<bars-view:>
|
219
|
+
<repeat>
|
220
|
+
<input:name/>
|
221
|
+
</repeat>
|
222
|
+
</bars-view:>
|
223
|
+
<field-list:>
|
224
|
+
</form>
|
225
|
+
|
226
|
+
# Controller Support
|
227
|
+
|
228
|
+
No special code is required in your controllers to support accessible
|
229
|
+
associations, even if you aren't using a Hobo controller.
|
230
|
+
|
231
|
+
# Validations
|
232
|
+
|
233
|
+
Validations simply work as you'd expect. The only thing to note is
|
234
|
+
that validation errors in a child object will cause the parent
|
235
|
+
object to receive an error message of "..." on the association.
|
236
|
+
|
237
|
+
# Transactions
|
238
|
+
|
239
|
+
Hobo's accessible associations do not do any explicit saves so any new
|
240
|
+
child objects are not saved until the parent object is saved. Rails
|
241
|
+
wraps this save in a transaction, so any save is an all or nothing
|
242
|
+
deal even though parents and children are saved via different SQL
|
243
|
+
statements.
|
244
|
+
|
245
|
+
# Rails 2.3 nested models
|
246
|
+
|
247
|
+
Rails 2.3 includes a functionality similar to Hobo's accessible
|
248
|
+
associations. The Hobo version is based on an early version of the
|
249
|
+
Rails functionality, but unfortunately the Rails version changed
|
250
|
+
significantly between the time that the Hobo version was released and
|
251
|
+
when Rails 2.3 was released.
|
252
|
+
|
253
|
+
For more information on Rails 2.3's `accept_nested_attributes_for`,
|
254
|
+
see [the Ruby on Rails
|
255
|
+
blog](http://weblog.rubyonrails.org/2009/1/26/nested-model-forms) or
|
256
|
+
[Ryan Daigle's blog](http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes).
|
257
|
+
|
258
|
+
The two versions use a different model: in Hobo the whole array is
|
259
|
+
assigned, allowing add, delete or update via a single mechanism. In
|
260
|
+
rails, there's a different mechanism for adding or deleting objects
|
261
|
+
from the association, and there's no method for update.
|
262
|
+
|
263
|
+
Each version have their pluses and minuses. The Hobo version is
|
264
|
+
conceptually simpler, but it starts to get unwieldy if there are a
|
265
|
+
large number of elements in the association.
|
266
|
+
|
267
|
+
Both mechanisms are compatible and may be enabled simultaneously.
|
268
|
+
|
269
|
+
It's certainly possible that Rapid >=1.1 will acquire tags that will
|
270
|
+
require `accept_nested_attributes_for`. However, it's unlikely that
|
271
|
+
Hobo will drop support for accessible associations unless ActiveRecord
|
272
|
+
itself changes significantly.
|
273
|
+
|
data/doctest/scopes.rdoctest
CHANGED
@@ -22,7 +22,7 @@ show_title = !show_link && (name_attribute || !has_body)
|
|
22
22
|
model_key = model.name.pluralize.underscore
|
23
23
|
-%>
|
24
24
|
<def tag="card" for="<%= model.name %>">
|
25
|
-
<card class="<%=
|
25
|
+
<card class="<%= model_class %>" param="default" merge>
|
26
26
|
<% if name_attribute || show_link || has_actions || !has_body -%>
|
27
27
|
<header: param>
|
28
28
|
<% if show_link || show_title -%>
|
@@ -21,7 +21,7 @@ model_key = model.name.tableize
|
|
21
21
|
-%>
|
22
22
|
|
23
23
|
<def tag="index-page" for="<%= model.name %>">
|
24
|
-
<page merge title="#{ht '<%= model_key %>.index.title', :default=>['<%= model_name :plural %>'] }">
|
24
|
+
<page merge title="#{ht '<%= model_key %>.index.title', :default=>['<%= sq_escape(model_name :plural) %>'] }">
|
25
25
|
<body: class="index-page <%= model_class %>" param/>
|
26
26
|
|
27
27
|
<content: param>
|
@@ -77,7 +77,7 @@ model_key = model.name.tableize
|
|
77
77
|
|
78
78
|
|
79
79
|
<def tag="new-page" for="<%= model.name %>">
|
80
|
-
<page merge title="#{ht '<%= model_key %>.new.title', :default=>['New <%=model_name %>'] }">
|
80
|
+
<page merge title="#{ht '<%= model_key %>.new.title', :default=>[' New <%= sq_escape(model_name) %>'] }">
|
81
81
|
<body: class="new-page <%= model_class %>" param/>
|
82
82
|
|
83
83
|
<content: param>
|
@@ -91,7 +91,7 @@ model_key = model.name.tableize
|
|
91
91
|
|
92
92
|
<section param="content-body">
|
93
93
|
<form param>
|
94
|
-
<submit: label="#{ht '<%= model_key %>.actions.create', :default=>['Create <%= model_name %>']}"/>
|
94
|
+
<submit: label="#{ht '<%= model_key %>.actions.create', :default=>['Create <%= sq_escape(model_name) %>']}"/>
|
95
95
|
</form>
|
96
96
|
</section>
|
97
97
|
</content:>
|
@@ -126,7 +126,7 @@ unless model.view_hints.secondary_children.empty?
|
|
126
126
|
end
|
127
127
|
-%>
|
128
128
|
<def tag="show-page" for="<%= model.name %>">
|
129
|
-
<page merge title="#{ht '<%=model_key %>.show.title', :default=>['<%=model_name %>'] }">
|
129
|
+
<page merge title="#{ht '<%=model_key %>.show.title', :default=>['<%=sq_escape model_name %>'] }">
|
130
130
|
|
131
131
|
<body: class="show-page <%= model_class %>" param/>
|
132
132
|
|
@@ -202,7 +202,7 @@ end
|
|
202
202
|
</h3>
|
203
203
|
<form with="&@<%= collection_class.name.underscore %> || new_for_current_user(@<%= model.name.underscore %>.<%= collection %>)" owner="<%= owner %>" without-cancel param>
|
204
204
|
<field-list: skip="<%= owner %>"/>
|
205
|
-
<submit: label="#{ht '<%= collection.to_s.pluralize %>.actions.add', :default=>['Add'] }"/>
|
205
|
+
<submit: label="#{ht '<%= sq_escape collection.to_s.pluralize %>.actions.add', :default=>['Add'] }"/>
|
206
206
|
</form>
|
207
207
|
</section>
|
208
208
|
<% end -%>
|
@@ -251,7 +251,7 @@ end
|
|
251
251
|
name_attribute = model.name_attribute
|
252
252
|
-%>
|
253
253
|
<def tag="edit-page" for="<%= model.name %>">
|
254
|
-
<page merge title="#{ht '<%= model_key %>.edit.title', :default=>['Edit <%=model_name %>'] }">
|
254
|
+
<page merge title="#{ht '<%= model_key %>.edit.title', :default=>['Edit <%= sq_escape model_name %>'] }">
|
255
255
|
|
256
256
|
<body: class="edit-page <%= model_class %>" param/>
|
257
257
|
|
@@ -262,7 +262,7 @@ name_attribute = model.name_attribute
|
|
262
262
|
Edit <type-name/>
|
263
263
|
</ht>
|
264
264
|
</h2>
|
265
|
-
<delete-button label="#{ht '<%= model_key %>.actions.delete', :default=>['Remove This <%= model_name %>']}" param/>
|
265
|
+
<delete-button label="#{ht '<%= model_key %>.actions.delete', :default=>['Remove This <%= sq_escape model_name %>']}" param/>
|
266
266
|
</section>
|
267
267
|
|
268
268
|
<section param="content-body">
|
@@ -290,7 +290,7 @@ new_link = :new.in?(actions)
|
|
290
290
|
-%>
|
291
291
|
<def tag="index-for-<%= owner.dasherize %>-page" polymorphic/>
|
292
292
|
<def tag="index-for-<%= owner.dasherize %>-page" for="<%= model.name %>">
|
293
|
-
<page merge title="#{ht '<%= model_key %>.index_for_owner.title', :default=>['<%=model_name :plural %> for']} #{name :with => @<%= owner %>, :no_wrapper => true}">
|
293
|
+
<page merge title="#{ht '<%= model_key %>.index_for_owner.title', :default=>['<%= sq_escape(model_name :plural) %> for']} #{name :with => @<%= owner %>, :no_wrapper => true}">
|
294
294
|
<body: class="index-for-owner-page <%= owner.dasherize %> <%= model_class %>" param/>
|
295
295
|
<content: param>
|
296
296
|
<header param="content-header">
|
@@ -348,7 +348,7 @@ new_link = :new.in?(actions)
|
|
348
348
|
<% if :new.in? actions -%>
|
349
349
|
<def tag="new-for-<%= owner.dasherize %>-page" polymorphic/>
|
350
350
|
<def tag="new-for-<%= owner.dasherize %>-page" for="<%= model.name %>">
|
351
|
-
<page merge title="#{ht '<%=model_key %>.new_for_owner.title', :default=>['New <%=model_name %> for']} #{name :with => @<%= owner %>}">
|
351
|
+
<page merge title="#{ht '<%=model_key %>.new_for_owner.title', :default=>['New <%= sq_escape model_name %> for']} #{name :with => @<%= owner %>}">
|
352
352
|
<body: class="new-for-owner-page <% owner.dasherize %> <%= model_class %>" param/>
|
353
353
|
|
354
354
|
<content: param>
|
@@ -369,7 +369,7 @@ new_link = :new.in?(actions)
|
|
369
369
|
<section param="content-body">
|
370
370
|
<form owner="<%= owner %>" method="post" param>
|
371
371
|
<field-list: skip="<%= owner %>"/>
|
372
|
-
<submit: label="#{ht '<%=model_key %>.actions.create', :default=>['Create <%= model_name %>']}"/>
|
372
|
+
<submit: label="#{ht '<%=model_key %>.actions.create', :default=>['Create <%= sq_escape model_name %>']}"/>
|
373
373
|
</form>
|
374
374
|
</section>
|
375
375
|
</content:>
|
@@ -383,7 +383,7 @@ new_link = :new.in?(actions)
|
|
383
383
|
<def tag="<%= creator.dasherize %>-page" polymorphic/>
|
384
384
|
<def tag="<%= creator.dasherize %>-page" for="<%= model.name %>">
|
385
385
|
|
386
|
-
<page title="#{ht '<%=model_key %>.<%= creator.underscore %>.title', :default=>['<%= creator.titleize %>']}" merge>
|
386
|
+
<page title="#{ht '<%=model_key %>.<%= creator.underscore %>.title', :default=>['<%= sq_escape creator.titleize %>']}" merge>
|
387
387
|
|
388
388
|
<body: class="lifecycle-start-page <%= creator.dasherize %>-page" param/>
|
389
389
|
|
@@ -25,6 +25,16 @@ module ActiveRecord
|
|
25
25
|
record
|
26
26
|
end
|
27
27
|
|
28
|
+
# DO NOT call super here - AssociationProxy's version loads the collection, and that's bad.
|
29
|
+
# TODO: this really belongs in Rails; migrate it there ASAP
|
30
|
+
def respond_to?(*args)
|
31
|
+
proxy_respond_to?(*args) || Array.new.respond_to?(*args)
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO: send this patch into Rails. There's no reason to load the collection just to find out it acts like an array.
|
35
|
+
def is_a?(klass)
|
36
|
+
Array.is_a?(klass)
|
37
|
+
end
|
28
38
|
|
29
39
|
def member_class
|
30
40
|
proxy_reflection.klass
|
data/lib/hobo.rb
CHANGED
@@ -16,7 +16,7 @@ class HoboError < RuntimeError; end
|
|
16
16
|
|
17
17
|
module Hobo
|
18
18
|
|
19
|
-
VERSION = "0.9.
|
19
|
+
VERSION = "0.9.104"
|
20
20
|
|
21
21
|
class PermissionDeniedError < RuntimeError; end
|
22
22
|
|
@@ -220,4 +220,3 @@ module ::Enumerable
|
|
220
220
|
alias_method_chain :group_by, :metadata
|
221
221
|
end
|
222
222
|
|
223
|
-
Hobo.enable if defined?(Rails)
|
@@ -161,14 +161,19 @@ module Hobo
|
|
161
161
|
def model_name(*options)
|
162
162
|
name = :plural.in?(options) ? model.view_hints.model_name_plural : model.view_hints.model_name
|
163
163
|
name = name.titleize.downcase if :lowercase.in?(options)
|
164
|
-
name = name.underscore.gsub('_', '-').gsub('/', '--') if :dashed.in?(options)
|
165
164
|
name = name.camelize if :camel.in?(options)
|
166
165
|
name
|
167
166
|
end
|
167
|
+
|
168
|
+
# escape single quotes and backslashes for use in a single
|
169
|
+
# quoted string
|
170
|
+
def sq_escape(s)
|
171
|
+
s.gsub(/[\\]/, "\\\\\\\\").gsub(/'/, "\\\\'")
|
172
|
+
end
|
168
173
|
|
169
174
|
|
170
175
|
def model_class
|
171
|
-
|
176
|
+
model.name.underscore.gsub('_', '-').gsub('/', '--')
|
172
177
|
end
|
173
178
|
|
174
179
|
|
@@ -397,7 +397,7 @@ module Hobo::Dryml
|
|
397
397
|
end
|
398
398
|
end
|
399
399
|
|
400
|
-
if param_name == :default && overriding_proc
|
400
|
+
if param_name == :default && overriding_proc && overriding_proc.arity>0
|
401
401
|
# :default content is handled specially
|
402
402
|
|
403
403
|
call_tag_parameter_with_default_content(the_tag, attributes, parameters[:default], overriding_proc)
|
@@ -449,6 +449,8 @@ module Hobo
|
|
449
449
|
def find_or_paginate(finder, options)
|
450
450
|
options = options.reverse_merge(:paginate => request_requires_pagination?)
|
451
451
|
do_pagination = options.delete(:paginate) && finder.respond_to?(:paginate)
|
452
|
+
finder = Array.wrap(options.delete(:scope)).inject(finder) { |a, v| a.send(*Array.wrap(v).flatten) }
|
453
|
+
|
452
454
|
options[:order] = :default unless options[:order] || finder.send(:scope, :find)._?[:order]
|
453
455
|
|
454
456
|
if do_pagination
|
data/lib/hobo/user.rb
CHANGED
@@ -43,6 +43,7 @@ module Hobo
|
|
43
43
|
attr_accessor :current_password, :password, :password_confirmation, :type => :password
|
44
44
|
|
45
45
|
before_save :encrypt_password
|
46
|
+
after_save :stash_current_password
|
46
47
|
|
47
48
|
never_show *AUTHENTICATION_FIELDS
|
48
49
|
|
@@ -148,6 +149,13 @@ module Hobo
|
|
148
149
|
self.crypted_password = encrypt(password)
|
149
150
|
end
|
150
151
|
|
152
|
+
# after filter that sets current_password so we can pass
|
153
|
+
# validate_current_password_when_changing_password if you save
|
154
|
+
# again. See
|
155
|
+
# https://hobo.lighthouseapp.com/projects/8324-hobo/tickets/590
|
156
|
+
def stash_current_password
|
157
|
+
@current_password ||= password
|
158
|
+
end
|
151
159
|
|
152
160
|
def changing_password?
|
153
161
|
!new_record? && !lifecycle_changing_password? &&
|
data/rails/init.rb
ADDED
@@ -481,9 +481,9 @@ Element.findContaining = function(el, tag) {
|
|
481
481
|
return null;
|
482
482
|
}
|
483
483
|
|
484
|
-
Element.
|
484
|
+
Element.Methods.childWithClass = function(el, klass) {
|
485
485
|
var ret=null;
|
486
|
-
|
486
|
+
el.childElements().each(function(el2) {
|
487
487
|
if(ret==null && el2.hasClassName(klass)) ret=el2;
|
488
488
|
});
|
489
489
|
return ret;
|
@@ -570,6 +570,14 @@ new HoboBehavior("ul.input-many", {
|
|
570
570
|
},
|
571
571
|
|
572
572
|
initialize: function(ul) {
|
573
|
+
/* the second clause should be sufficient, but it isn't in IE7. See bug 603 */
|
574
|
+
$$(".input-many-template input:hidden, .input-many-template select:hidden, .input-many-template textarea:hidden, .input-many-template button:hidden").each(function(input) {
|
575
|
+
if(!input.disabled) {
|
576
|
+
input.disabled = true;
|
577
|
+
input.addClassName("input_many_template_input");
|
578
|
+
}
|
579
|
+
});
|
580
|
+
|
573
581
|
// disable all elements inside our template, and mark them so we can find them later.
|
574
582
|
$$(".input-many-template input:enabled, .input-many-template select:enabled, .input-many-template textarea:enabled, .input-many-template button:enabled").each(function(input) {
|
575
583
|
input.disabled = true;
|
@@ -611,7 +619,7 @@ new HoboBehavior("ul.input-many", {
|
|
611
619
|
|
612
620
|
// given this==an input-many item, get the submit index
|
613
621
|
getIndex: function() {
|
614
|
-
return Number(this.id.match(/\[([0-9])
|
622
|
+
return Number(this.id.match(/\[([-0-9]+)\]$/)[1]);
|
615
623
|
},
|
616
624
|
|
617
625
|
/* For some reason, select() and down() and all those useful functions aren't working for us. Roll our own replacement. */
|
@@ -627,6 +635,11 @@ new HoboBehavior("ul.input-many", {
|
|
627
635
|
Event.stop(ev);
|
628
636
|
var ul = el.up('ul.input-many'), li = el.up('li.input-many-li');
|
629
637
|
|
638
|
+
if(li.id.search(/\[-1\]/)>=0) {
|
639
|
+
/* if(console) console.log("IE7 messed up again (bug 605)"); */
|
640
|
+
return;
|
641
|
+
}
|
642
|
+
|
630
643
|
var template = ul.down("li.input-many-template");
|
631
644
|
var clone = $(template.cloneNode(true));
|
632
645
|
clone.removeClassName("input-many-template");
|
@@ -652,7 +665,12 @@ new HoboBehavior("ul.input-many", {
|
|
652
665
|
// do the add with anim
|
653
666
|
clone.setStyle("display", "none")
|
654
667
|
li.insert({after: clone});
|
655
|
-
new Effect.BlindDown(clone, {duration: 0.3
|
668
|
+
new Effect.BlindDown(clone, {duration: 0.3, afterFinish: function(ef) {
|
669
|
+
Event.addBehavior.reload();
|
670
|
+
|
671
|
+
ul.fire("rapid:add", { element: clone });
|
672
|
+
ul.fire("rapid:change", { element: clone });
|
673
|
+
}});
|
656
674
|
|
657
675
|
// visibility
|
658
676
|
if(li.hasClassName("empty")) {
|
@@ -664,11 +682,6 @@ new HoboBehavior("ul.input-many", {
|
|
664
682
|
li.childWithClass("buttons").childWithClass("add-item").addClassName("hidden");
|
665
683
|
}
|
666
684
|
|
667
|
-
Event.addBehavior.reload();
|
668
|
-
|
669
|
-
ul.fire("rapid:add", { element: clone })
|
670
|
-
ul.fire("rapid:change", { element: clone })
|
671
|
-
|
672
685
|
return;
|
673
686
|
},
|
674
687
|
|
@@ -678,7 +691,12 @@ new HoboBehavior("ul.input-many", {
|
|
678
691
|
var ul = el.up('ul.input-many'), li = el.up('li.input-many-li')
|
679
692
|
var minimum = parseInt(Hobo.getClassData(ul, 'minimum'));
|
680
693
|
|
681
|
-
|
694
|
+
if(li.id.search(/\[-1\]/)>=0) {
|
695
|
+
/* if(console) console.log("IE7 messed up again (bug 605)"); */
|
696
|
+
return;
|
697
|
+
}
|
698
|
+
|
699
|
+
if(ul.fire("rapid:remove", { element: li }).stopped) return;
|
682
700
|
|
683
701
|
// rename everybody from me onwards
|
684
702
|
var i=this.getIndex.call(li)
|
@@ -708,11 +726,11 @@ new HoboBehavior("ul.input-many", {
|
|
708
726
|
}
|
709
727
|
|
710
728
|
new Effect.BlindUp(li, { duration: 0.3, afterFinish: function (ef) {
|
729
|
+
ul.fire("rapid:change")
|
711
730
|
li.remove()
|
712
731
|
} });
|
713
732
|
|
714
|
-
|
715
|
-
},
|
733
|
+
}
|
716
734
|
|
717
735
|
|
718
736
|
|
data/taglibs/rapid_core.dryml
CHANGED
@@ -36,11 +36,11 @@
|
|
36
36
|
input_attrs = {:no_edit => no_edit} if tag == "input"
|
37
37
|
-%>
|
38
38
|
<labelled-item unless="&tag == 'input' && no_edit == 'skip' && !can_edit?">
|
39
|
-
<item-label param="#{
|
39
|
+
<item-label param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-label" unless="&field_name.blank?">
|
40
40
|
<do param="label"><%= field_name %></do>
|
41
41
|
</item-label>
|
42
|
-
<item-value param="#{
|
43
|
-
<do param="view"><call-tag tag="&tag" param="#{
|
42
|
+
<item-value param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-view" colspan="&2 if field_name.blank?">
|
43
|
+
<do param="view"><call-tag tag="&tag" param="#{scope.field_name.to_s.sub('?', '').sub('.', '-')}-tag" merge-attrs="&input_attrs"/></do>
|
44
44
|
<div param="input-help" if="&tag.to_sym == :input && !this_field_help.blank?"><%= this_field_help %></div>
|
45
45
|
</item-value>
|
46
46
|
</labelled-item>
|
@@ -119,7 +119,7 @@ This will use `<input/>` as the tag in each table cell instead of `<view/>`
|
|
119
119
|
class="#{scope.even_odd} #{this_type.name.underscore} #{model_id_class}">
|
120
120
|
<if test="&fields">
|
121
121
|
<with-fields merge-attrs="&all_attributes & attrs_for(:with_fields)" force-all>
|
122
|
-
<td param="#{
|
122
|
+
<td param="#{scope.field_name.to_s.sub('?', '').gsub('.', '-')}-view"><call-tag tag="&field_tag"/></td>
|
123
123
|
</with-fields>
|
124
124
|
<td class="controls" param="controls" if="&all_parameters[:controls]">
|
125
125
|
<a param="edit-link" action="edit" if="&can_edit?"><ht key="hobo.action.edit">Edit</ht></a>
|
@@ -616,5 +616,6 @@ The context should be a user object. If `this == current_user` the "you" form is
|
|
616
616
|
first-option="Guest" options="&user.all(:limit => 30).*.login"
|
617
617
|
onchange="location.href = '#{dev_support_path}/set_current_user?login=' + this.options[this.selectedIndex].value"
|
618
618
|
selected="#{current_user.login}"
|
619
|
-
class="dev-user-changer"
|
619
|
+
class="dev-user-changer"
|
620
|
+
merge-attrs/>
|
620
621
|
</def>
|
data/taglibs/rapid_forms.dryml
CHANGED
@@ -118,7 +118,21 @@ AJAX based submission can be enabled by simply adding an `update` attribute. e.g
|
|
118
118
|
- reset-form: Clear the form after submission (only makes sense for ajax forms)
|
119
119
|
|
120
120
|
- refocus-form: Refocus the first form-field after submission (only makes sense for ajax forms)
|
121
|
-
|
121
|
+
|
122
|
+
### Parameters
|
123
|
+
|
124
|
+
The standard form tag does not have any parameters, nor does it have any default content. However, Hobo does autogenerate polymorphic form tags for each of your models into `app/views/taglibs/auto/rapid/forms.dryml`. These forms have the following parameters:
|
125
|
+
|
126
|
+
- error-messages
|
127
|
+
|
128
|
+
- field-list
|
129
|
+
|
130
|
+
- actions
|
131
|
+
|
132
|
+
- submit
|
133
|
+
|
134
|
+
- cancel
|
135
|
+
|
122
136
|
-->
|
123
137
|
<def tag="form" polymorphic attrs="update, hidden-fields, action, method, web-method, lifecycle, owner, multipart"><%=
|
124
138
|
ajax_attrs, html_attrs = attributes.partition_hash(Hobo::RapidHelper::AJAX_ATTRS)
|
@@ -436,7 +450,7 @@ procedure call.
|
|
436
450
|
The URL that the call is POSTed to is the `object_url` of `this`, plus the method name
|
437
451
|
|
438
452
|
`<remote-method-button>` supports all of the standard ajax attributes (see the main taglib documention for Rapid
|
439
|
-
Forms). If any ajax attributes are given, the button becomes an ajax button, if not,
|
453
|
+
Forms). If any ajax attributes are given, the button becomes an ajax button, if not, Rails' `button_to` is used, which behaves similarly to a standard link.
|
440
454
|
|
441
455
|
### Attributes
|
442
456
|
|
@@ -910,8 +924,33 @@ A fully worked up example of nested hjq-input-many's may be found in [agility/jq
|
|
910
924
|
|
911
925
|
- `template`: the default values for new items. Normally this functionality is better provided by Model.new, but it's here if you need it.
|
912
926
|
|
927
|
+
### Events
|
928
|
+
|
929
|
+
- `rapid:add`: fired after the element is inserted. `memo.element`
|
930
|
+
is set to the new element inserted.
|
931
|
+
|
932
|
+
- `rapid:remove`: fired before the element is
|
933
|
+
inserted. `memo.element` is set to the element to be deleted. The
|
934
|
+
removal may be cancelled by stopping the event.
|
935
|
+
|
936
|
+
- `rapid:change`: fired after an element has been removed or
|
937
|
+
inserted. `memo.element` set as above.
|
938
|
+
|
939
|
+
Example javascript:
|
940
|
+
|
941
|
+
var last_added;
|
942
|
+
var last_removed;
|
943
|
+
Event.addBehavior({
|
944
|
+
'.stories:rapid:add' : function(ev) {
|
945
|
+
last_added = ev.memo.element;
|
946
|
+
},
|
947
|
+
'.stories:rapid:remove' : function(ev) {
|
948
|
+
if(!confirm("really?")) ev.stop();
|
949
|
+
}
|
950
|
+
});
|
951
|
+
|
913
952
|
-->
|
914
|
-
<def tag="input-many" attrs="minimum, fields, skip, template
|
953
|
+
<def tag="input-many" attrs="minimum, fields, skip, template" polymorphic >
|
915
954
|
<%
|
916
955
|
# helper function to create id's on buttons to facilitate testing
|
917
956
|
def underize(s)
|
@@ -922,7 +961,7 @@ end
|
|
922
961
|
<% template ||= this.try.new_candidate || this.member_class.new %>
|
923
962
|
<% minimum ||= 0 ; minimum = minimum.to_i %>
|
924
963
|
<% skip ||= this.proxy_reflection.klass.reflect_on_all_associations.detect {|p| p.primary_key_name==this.proxy_reflection.primary_key_name}.try.name.to_s if this.respond_to? :proxy_reflection %>
|
925
|
-
<ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)}
|
964
|
+
<ul class="input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} #{css_data(:minimum, minimum)}">
|
926
965
|
<fake-field-context fake-field="-1" context="&template">
|
927
966
|
<li class="input-many-li input-many-template" id="#{param_name_for_this}">
|
928
967
|
<div class="input-many-item" param="default">
|
data/taglibs/rapid_summary.dryml
CHANGED
@@ -129,8 +129,7 @@
|
|
129
129
|
|
130
130
|
<!-- repeats on the plugins used by the application -->
|
131
131
|
<def tag="with-plugins">
|
132
|
-
<%
|
133
|
-
plugins = Rails.configuration.plugin_loader.new(fi).plugins %>
|
132
|
+
<% plugins = Rails.configuration.plugin_loader.new(Hobo.rails_initializer).plugins %>
|
134
133
|
<repeat with="&plugins">
|
135
134
|
<do param="default" />
|
136
135
|
</repeat>
|
data/taglibs/rapid_support.dryml
CHANGED
@@ -44,11 +44,13 @@ This tag is in need of a review - it's a bit funky.
|
|
44
44
|
field_names -= comma_split(skip) if skip
|
45
45
|
field_names = field_names.select {|f| can_view?(this, f)} unless force_all
|
46
46
|
field_names.each do |field|
|
47
|
+
%><set-scoped field-name="&field"><%
|
47
48
|
if field == "this"
|
48
49
|
%><do param="default"/><%
|
49
50
|
else
|
50
51
|
%><with field="&field"><do param="default"/></with><%
|
51
52
|
end
|
53
|
+
%></set-scoped><%
|
52
54
|
end
|
53
55
|
%></def>
|
54
56
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hobo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.9.
|
4
|
+
version: 0.9.104
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tom Locke
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date:
|
12
|
+
date: 2010-01-21 00:00:00 -05:00
|
13
13
|
default_executable: hobo
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
requirements:
|
41
41
|
- - "="
|
42
42
|
- !ruby/object:Gem::Version
|
43
|
-
version: 0.9.
|
43
|
+
version: 0.9.104
|
44
44
|
version:
|
45
45
|
- !ruby/object:Gem::Dependency
|
46
46
|
name: hobofields
|
@@ -50,7 +50,7 @@ dependencies:
|
|
50
50
|
requirements:
|
51
51
|
- - "="
|
52
52
|
- !ruby/object:Gem::Version
|
53
|
-
version: 0.9.
|
53
|
+
version: 0.9.104
|
54
54
|
version:
|
55
55
|
description:
|
56
56
|
email: tom@tomlocke.com
|
@@ -68,11 +68,12 @@ files:
|
|
68
68
|
- bin/hobo
|
69
69
|
- doctest/hobo/hobo_helper.rdoctest
|
70
70
|
- doctest/hobo/lifecycles.rdoctest
|
71
|
+
- doctest/model.rdoctest
|
72
|
+
- doctest/multi_model_forms.rdoctest
|
71
73
|
- doctest/scopes.rdoctest
|
72
74
|
- dryml_generators/rapid/cards.dryml.erb
|
73
75
|
- dryml_generators/rapid/forms.dryml.erb
|
74
76
|
- dryml_generators/rapid/pages.dryml.erb
|
75
|
-
- init.rb
|
76
77
|
- lib/action_view_extensions/helpers/tag_helper.rb
|
77
78
|
- lib/active_record/association_collection.rb
|
78
79
|
- lib/active_record/association_proxy.rb
|
@@ -104,7 +105,6 @@ files:
|
|
104
105
|
- lib/hobo/dryml/template.rb
|
105
106
|
- lib/hobo/dryml/template_environment.rb
|
106
107
|
- lib/hobo/dryml/template_handler.rb
|
107
|
-
- lib/hobo/fake_initializer.rb
|
108
108
|
- lib/hobo/find_for.rb
|
109
109
|
- lib/hobo/generator.rb
|
110
110
|
- lib/hobo/guest.rb
|
@@ -135,6 +135,7 @@ files:
|
|
135
135
|
- lib/hobo/user.rb
|
136
136
|
- lib/hobo/user_controller.rb
|
137
137
|
- lib/hobo/view_hints.rb
|
138
|
+
- rails/init.rb
|
138
139
|
- rails_generators/hobo/USAGE
|
139
140
|
- rails_generators/hobo/hobo_generator.rb
|
140
141
|
- rails_generators/hobo/templates/application.css
|
data/init.rb
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
# really what we want is a reference to the Initializer used in
|
2
|
-
# config/boot.rb. But since we can't monkey patch that file, we'll
|
3
|
-
# use a fake instead.
|
4
|
-
|
5
|
-
# this is used by the rapid_summary tag with_plugins
|
6
|
-
module Hobo
|
7
|
-
class FakeInitializer
|
8
|
-
attr_reader :configuration
|
9
|
-
|
10
|
-
def initialize(config = Rails.configuration)
|
11
|
-
@configuration = config
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|