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