giraffesoft-attribute_fu 0.2
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/MIT-LICENSE +20 -0
- data/README +115 -0
- data/Rakefile +22 -0
- data/init.rb +2 -0
- data/lib/attribute_fu.rb +2 -0
- data/lib/attribute_fu/associated_form_helper.rb +139 -0
- data/lib/attribute_fu/associations.rb +124 -0
- data/tasks/attribute_fu_tasks.rake +4 -0
- data/test/Rakefile +10 -0
- data/test/app/controllers/application.rb +10 -0
- data/test/app/helpers/application_helper.rb +3 -0
- data/test/app/models/comment.rb +8 -0
- data/test/app/models/photo.rb +3 -0
- data/test/config/boot.rb +97 -0
- data/test/config/database.yml +15 -0
- data/test/config/environment.rb +15 -0
- data/test/config/environments/development.rb +18 -0
- data/test/config/environments/test.rb +22 -0
- data/test/config/routes.rb +35 -0
- data/test/db/migrate/001_create_photos.rb +14 -0
- data/test/db/migrate/002_create_comments.rb +15 -0
- data/test/db/schema.rb +29 -0
- data/test/script/console +3 -0
- data/test/script/destroy +3 -0
- data/test/script/generate +3 -0
- data/test/script/server +3 -0
- data/test/test/test_helper.rb +6 -0
- data/test/test/unit/associated_form_helper_test.rb +376 -0
- data/test/test/unit/comment_test.rb +6 -0
- data/test/test/unit/photo_test.rb +149 -0
- data/test/vendor/plugins/shoulda/init.rb +3 -0
- data/test/vendor/plugins/shoulda/lib/shoulda.rb +20 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/active_record_helpers.rb +338 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/context.rb +143 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/general.rb +119 -0
- data/test/vendor/plugins/shoulda/lib/shoulda/private_helpers.rb +17 -0
- data/uninstall.rb +1 -0
- metadata +110 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 James Golick
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
= AttributeFu
|
2
|
+
|
3
|
+
|
4
|
+
Creating multi-model forms is amazingly easy with AttributeFu.
|
5
|
+
|
6
|
+
= Get It!
|
7
|
+
|
8
|
+
$ piston import http://svn.jamesgolick.com/attribute_fu/tags/stable vendor/plugins/attribute_fu
|
9
|
+
|
10
|
+
= Conventions
|
11
|
+
|
12
|
+
attribute_fu requires the fewest keystrokes if you follow certain conventions.
|
13
|
+
|
14
|
+
* The partial that contains your associated model's form is expected to be called _class_name.template_ext
|
15
|
+
(e.g. the partial for your Task model would be called _task.html.erb)
|
16
|
+
* The DOM element that contains the form for your model should have the CSS class .class_name
|
17
|
+
(e.g. the CSS class for your Task would be .task)
|
18
|
+
* The DOM element that contains all of the rendered forms should have the DOM ID #class_name
|
19
|
+
(e.g. the DOM ID of the container of your Task forms would be #tasks)
|
20
|
+
<i>Note: This is only relevant if using the add_associated_link method.</i>
|
21
|
+
|
22
|
+
= Example
|
23
|
+
|
24
|
+
In this example, you'll build a form for a Project model, in which a list of associated (has_many) tasks can be edited.
|
25
|
+
|
26
|
+
The first thing you need to do is enable attributes on the association.
|
27
|
+
|
28
|
+
class Project < ActiveRecord::Base
|
29
|
+
has_many :tasks, :attributes => true
|
30
|
+
end
|
31
|
+
|
32
|
+
Instances of Project will now respond to task_attributes, whose format is as follows:
|
33
|
+
|
34
|
+
@project.task_attributes = {
|
35
|
+
@project.tasks.first.id => {:title => "A new title for an existing task"},
|
36
|
+
:new => {
|
37
|
+
"0" => {:title => "A new task"}
|
38
|
+
}
|
39
|
+
}
|
40
|
+
|
41
|
+
Any tasks that already exist in that collection, and are not included in the hash, as supplied to task_attributes, will be removed from the association when saved. Most of the time, the form helpers should take care of building that hash for you, though.
|
42
|
+
|
43
|
+
== Form Helpers
|
44
|
+
|
45
|
+
If you follow certain conventions, rendering your associated model's form elements is incredibly simple. The partial should have the name of the associated element's type, and look like a regular old form partial (no messy fields_for calls, or any nonsense like that).
|
46
|
+
|
47
|
+
## _task.html.erb
|
48
|
+
<div class="task">
|
49
|
+
<label>Title</label>
|
50
|
+
<%= f.text_field :title %>
|
51
|
+
</div>
|
52
|
+
|
53
|
+
Then, in your parent element's form, call the render_associated_form method on the form builder, with the collection of elements you'd like to render as the only argument.
|
54
|
+
|
55
|
+
## _form.html.erb
|
56
|
+
<%= f.render_associated_form(@project.tasks) %>
|
57
|
+
|
58
|
+
That call will render the partial named _task.html.erb with each element in the supplied collection of tasks, wrapping the partial in a form builder (fields_for) with all the necessary arguments to produce a hash that will satisfy the task_attributes method.
|
59
|
+
|
60
|
+
You may want to add a few blank tasks to the bottom of your form; no need to do that in the controller anymore.
|
61
|
+
|
62
|
+
<%= f.render_associated_form(@project.tasks, :new => 3) %>
|
63
|
+
|
64
|
+
Since this is Web2.0, no form would be complete without some DHTML add and remove buttons. Fortunately, there are some nifty helpers to create them for us. Simply calling remove_link on the form builder in your _task partial will do the trick.
|
65
|
+
|
66
|
+
## _task.html.erb
|
67
|
+
<div class="task">
|
68
|
+
<label>Title</label>
|
69
|
+
<%= f.text_field :title %>
|
70
|
+
<%= f.remove_link "remove" %>
|
71
|
+
</div>
|
72
|
+
|
73
|
+
Creating the add button is equally simple. The add_associated_link helper will do all of the heavy lifting for you.
|
74
|
+
|
75
|
+
## _form.html.erb
|
76
|
+
<%= f.add_associated_link "Add New Task", @project.tasks.build %>
|
77
|
+
|
78
|
+
That's all you have to do to create a multi-model form with attribute_fu!
|
79
|
+
|
80
|
+
== Discarding Blank Child Models
|
81
|
+
|
82
|
+
If you want to show a bunch of blank child model forms at the bottom of your form, but you only want to save the ones that are filled out, you can use the discard_if option. It accepts either a proc:
|
83
|
+
|
84
|
+
class Project < ActiveRecord::Base
|
85
|
+
has_many :tasks, :attributes => true, :discard_if => proc { |task| task.title.blank? }
|
86
|
+
end
|
87
|
+
|
88
|
+
...or a symbol...
|
89
|
+
|
90
|
+
class Project < ActiveRecord::Base
|
91
|
+
has_many :tasks, :attributes => true, :discard_if => :blank?
|
92
|
+
end
|
93
|
+
|
94
|
+
class Task < ActiveRecord::Base
|
95
|
+
def blank?
|
96
|
+
title.blank?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
Using a symbol allows you to keep code DRYer if you are using that routine in more than one place. Both of those examples, however, would have the same effect.
|
101
|
+
|
102
|
+
= Updates
|
103
|
+
|
104
|
+
Come join the discussion on the {mailing list}[link:http://groups.google.com/group/attribute_fu]
|
105
|
+
|
106
|
+
Updates will be available {here}[http://jamesgolick.com/attribute_fu]
|
107
|
+
|
108
|
+
|
109
|
+
== Credits
|
110
|
+
|
111
|
+
attribute_fu was created, and is maintained by {James Golick}[http://jamesgolick.com].
|
112
|
+
|
113
|
+
|
114
|
+
|
115
|
+
Copyright (c) 2007 James Golick, GiraffeSoft Inc., released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the attribute_fu plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the attribute_fu plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'AttributeFu'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
data/init.rb
ADDED
data/lib/attribute_fu.rb
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
module AttributeFu
|
2
|
+
# Methods for building forms that contain fields for associated models.
|
3
|
+
#
|
4
|
+
# Refer to the Conventions section in the README for the various expected defaults.
|
5
|
+
#
|
6
|
+
module AssociatedFormHelper
|
7
|
+
# Works similarly to fields_for, but used for building forms for associated objects.
|
8
|
+
#
|
9
|
+
# Automatically names fields to be compatible with the association_attributes= created by attribute_fu.
|
10
|
+
#
|
11
|
+
# An options hash can be specified to override the default behaviors.
|
12
|
+
#
|
13
|
+
# Options are:
|
14
|
+
# <tt>:javascript</tt> - Generate id placeholders for use with Prototype's Template class (this is how attribute_fu's add_associated_link works).
|
15
|
+
# <tt>:name</tt> - Specify the singular name of the association (in singular form), if it differs from the class name of the object.
|
16
|
+
#
|
17
|
+
# Any other supplied parameters are passed along to fields_for.
|
18
|
+
#
|
19
|
+
# Note: It is preferable to call render_associated_form, which will automatically wrap your form partial in a fields_for_associated call.
|
20
|
+
#
|
21
|
+
def fields_for_associated(associated, *args, &block)
|
22
|
+
conf = args.last.is_a?(Hash) ? args.last : {}
|
23
|
+
associated_name = extract_option_or_class_name(conf, :name, associated)
|
24
|
+
name = associated_base_name associated_name
|
25
|
+
|
26
|
+
unless associated.new_record?
|
27
|
+
name << "[#{associated.new_record? ? 'new' : associated.id}]"
|
28
|
+
else
|
29
|
+
@new_objects ||= {}
|
30
|
+
@new_objects[associated_name] ||= -1 # we want naming to start at 0
|
31
|
+
identifier = !conf.nil? && conf[:javascript] ? '#{number}' : @new_objects[associated_name]+=1
|
32
|
+
|
33
|
+
name << "[new][#{identifier}]"
|
34
|
+
end
|
35
|
+
|
36
|
+
@template.fields_for(name, *args.unshift(associated), &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Creates a link for removing an associated element from the form, by removing its containing element from the DOM.
|
40
|
+
#
|
41
|
+
# Must be called from within an associated form.
|
42
|
+
#
|
43
|
+
# An options hash can be specified to override the default behaviors.
|
44
|
+
#
|
45
|
+
# Options are:
|
46
|
+
# * <tt>:selector</tt> - The CSS selector with which to find the element to remove.
|
47
|
+
# * <tt>:function</tt> - Additional javascript to be executed before the element is removed.
|
48
|
+
#
|
49
|
+
# Any remaining options are passed along to link_to_function
|
50
|
+
#
|
51
|
+
def remove_link(name, *args)
|
52
|
+
options = args.extract_options!
|
53
|
+
|
54
|
+
css_selector = options.delete(:selector) || ".#{@object.class.name.split("::").last.underscore}"
|
55
|
+
function = options.delete(:function) || ""
|
56
|
+
|
57
|
+
function << "$(this).up('#{css_selector}').remove()"
|
58
|
+
|
59
|
+
@template.link_to_function(name, function, *args.push(options))
|
60
|
+
end
|
61
|
+
|
62
|
+
# Creates a link that adds a new associated form to the page using Javascript.
|
63
|
+
#
|
64
|
+
# Must be called from within an associated form.
|
65
|
+
#
|
66
|
+
# Must be provided with a new instance of the associated object.
|
67
|
+
#
|
68
|
+
# e.g. f.add_associated_link 'Add Task', @project.tasks.build
|
69
|
+
#
|
70
|
+
# An options hash can be specified to override the default behaviors.
|
71
|
+
#
|
72
|
+
# Options are:
|
73
|
+
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
|
74
|
+
# * <tt>:container</tt> - specify the DOM id of the container in which to insert the new element.
|
75
|
+
# * <tt>:expression</tt> - specify a javascript expression with which to select the container to insert the new form in to (i.e. $(this).up('.tasks'))
|
76
|
+
# * <tt>:name</tt> - specify an alternate class name for the associated model (underscored)
|
77
|
+
#
|
78
|
+
# Any additional options are forwarded to link_to_function. See its documentation for available options.
|
79
|
+
#
|
80
|
+
def add_associated_link(name, object, opts = {})
|
81
|
+
associated_name = extract_option_or_class_name(opts, :name, object)
|
82
|
+
variable = "attribute_fu_#{associated_name}_count"
|
83
|
+
|
84
|
+
opts.symbolize_keys!
|
85
|
+
partial = opts.delete(:partial) || associated_name
|
86
|
+
container = opts.delete(:expression) || "'#{opts.delete(:container) || associated_name.pluralize}'"
|
87
|
+
|
88
|
+
form_builder = self # because the value of self changes in the block
|
89
|
+
|
90
|
+
@template.link_to_function(name, opts) do |page|
|
91
|
+
page << "if (typeof #{variable} == 'undefined') #{variable} = 0;"
|
92
|
+
page << "new Insertion.Bottom(#{container}, new Template("+form_builder.render_associated_form(object, :fields_for => { :javascript => true }, :partial => partial).to_json+").evaluate({'number': --#{variable}}))"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Renders the form of an associated object, wrapping it in a fields_for_associated call.
|
97
|
+
#
|
98
|
+
# The associated argument can be either an object, or a collection of objects to be rendered.
|
99
|
+
#
|
100
|
+
# An options hash can be specified to override the default behaviors.
|
101
|
+
#
|
102
|
+
# Options are:
|
103
|
+
# * <tt>:new</tt> - specify a certain number of new elements to be added to the form. Useful for displaying a
|
104
|
+
# few blank elements at the bottom.
|
105
|
+
# * <tt>:name</tt> - override the name of the association, both for the field names, and the name of the partial
|
106
|
+
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
|
107
|
+
# * <tt>:fields_for</tt> - specify additional options for the fields_for_associated call
|
108
|
+
# * <tt>:locals</tt> - specify additional variables to be passed along to the partial
|
109
|
+
# * <tt>:render</tt> - specify additional options to be passed along to the render :partial call
|
110
|
+
#
|
111
|
+
def render_associated_form(associated, opts = {})
|
112
|
+
associated = associated.is_a?(Array) ? associated : [associated] # preserve association proxy if this is one
|
113
|
+
|
114
|
+
opts.symbolize_keys!
|
115
|
+
(opts[:new] - associated.select(&:new_record?).length).times { associated.build } if opts[:new]
|
116
|
+
|
117
|
+
unless associated.empty?
|
118
|
+
name = extract_option_or_class_name(opts, :name, associated.first)
|
119
|
+
partial = opts[:partial] || name
|
120
|
+
local_assign_name = partial.split('/').last.split('.').first
|
121
|
+
|
122
|
+
associated.map do |element|
|
123
|
+
fields_for_associated(element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
|
124
|
+
@template.render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => element, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
def associated_base_name(associated_name)
|
132
|
+
"#{@object_name}[#{associated_name}_attributes]"
|
133
|
+
end
|
134
|
+
|
135
|
+
def extract_option_or_class_name(hash, option, object)
|
136
|
+
(hash.delete(option) || object.class.name.split('::').last.underscore).to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module AttributeFu
|
2
|
+
module Associations #:nodoc:
|
3
|
+
|
4
|
+
def self.included(base) #:nodoc:
|
5
|
+
base.class_eval do
|
6
|
+
extend ClassMethods
|
7
|
+
class << self; alias_method_chain :has_many, :association_option; end
|
8
|
+
|
9
|
+
class_inheritable_accessor :managed_association_attributes
|
10
|
+
write_inheritable_attribute :managed_association_attributes, {}
|
11
|
+
|
12
|
+
after_update :save_managed_associations
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(method_name, *args) #:nodoc:
|
17
|
+
if method_name.to_s =~ /.+?\_attributes=/
|
18
|
+
association_name = method_name.to_s.gsub '_attributes=', ''
|
19
|
+
association = managed_association_attributes.keys.detect { |element| element == association_name.to_sym } || managed_association_attributes.keys.detect { |element| element == association_name.pluralize.to_sym }
|
20
|
+
|
21
|
+
unless association.nil?
|
22
|
+
has_many_attributes association, args.first
|
23
|
+
|
24
|
+
return
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
super
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def has_many_attributes(association_id, attributes) #:nodoc:
|
33
|
+
association = send(association_id)
|
34
|
+
attributes = {} unless attributes.is_a? Hash
|
35
|
+
|
36
|
+
attributes.symbolize_keys!
|
37
|
+
|
38
|
+
if attributes.has_key?(:new)
|
39
|
+
new_attrs = attributes.delete(:new)
|
40
|
+
new_attrs = new_attrs.sort do |a,b|
|
41
|
+
value = lambda { |i| i < 0 ? i.abs + new_attrs.length : i }
|
42
|
+
|
43
|
+
value.call(a.first.to_i) <=> value.call(b.first.to_i)
|
44
|
+
end
|
45
|
+
new_attrs.each { |i, new_attrs| association.build new_attrs }
|
46
|
+
end
|
47
|
+
|
48
|
+
attributes.stringify_keys!
|
49
|
+
instance_variable_set removal_variable_name(association_id), association.reject { |object| object.new_record? || attributes.has_key?(object.id.to_s) }.map(&:id)
|
50
|
+
attributes.each do |id, object_attrs|
|
51
|
+
object = association.detect { |associated| associated.id.to_s == id }
|
52
|
+
object.attributes = object_attrs unless object.nil?
|
53
|
+
end
|
54
|
+
|
55
|
+
# discard blank attributes if discard_if proc exists
|
56
|
+
unless (discard = managed_association_attributes[association_id][:discard_if]).nil?
|
57
|
+
association.reject! { |object| object.new_record? && discard.call(object) }
|
58
|
+
association.delete(*association.select { |object| discard.call(object) })
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def save_managed_associations #:nodoc:
|
63
|
+
if managed_association_attributes != nil
|
64
|
+
managed_association_attributes.keys.each do |association_id|
|
65
|
+
association = send(association_id)
|
66
|
+
association.each(&:save)
|
67
|
+
|
68
|
+
unless (objects_to_remove = instance_variable_get removal_variable_name(association_id)).nil?
|
69
|
+
objects_to_remove.each { |remove_id| association.delete association.detect { |obj| obj.id.to_s == remove_id.to_s } }
|
70
|
+
instance_variable_set removal_variable_name(association_id), nil
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def removal_variable_name(association_id) #:nodoc:
|
77
|
+
"@#{association_id.to_s.pluralize}_to_remove"
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
|
82
|
+
# Behaves identically to the regular has_many, except adds the option <tt>:attributes</tt>, which, if true, creates
|
83
|
+
# a method called association_id_attributes (i.e. task_attributes, or comment_attributes) for setting the attributes
|
84
|
+
# of a collection of associated models.
|
85
|
+
#
|
86
|
+
# It also adds the option <tt>:discard_if</tt>, which accepts a proc or a symbol. If the proc evaluates to true, the
|
87
|
+
# child model will be discarded. The symbol is sent as a message to the child model instance; if it returns true,
|
88
|
+
# the child model will be discarded.
|
89
|
+
#
|
90
|
+
# e.g.
|
91
|
+
#
|
92
|
+
# :discard_if => proc { |comment| comment.title.blank? }
|
93
|
+
# or
|
94
|
+
# :discard_if => :blank? # where blank is defined in Comment
|
95
|
+
#
|
96
|
+
#
|
97
|
+
# The format is as follows:
|
98
|
+
#
|
99
|
+
# @project.task_attributes = {
|
100
|
+
# @project.tasks.first.id => {:title => "A new title for an existing task"},
|
101
|
+
# :new => {
|
102
|
+
# "0" => {:title => "A new task"}
|
103
|
+
# }
|
104
|
+
# }
|
105
|
+
#
|
106
|
+
# Any existing tasks that are not present in the attributes hash will be removed from the association when the (parent) model
|
107
|
+
# is saved.
|
108
|
+
#
|
109
|
+
def has_many_with_association_option(association_id, options = {}, &extension)
|
110
|
+
unless (config = options.delete(:attributes)).nil?
|
111
|
+
managed_association_attributes[association_id] = {}
|
112
|
+
if options.has_key?(:discard_if)
|
113
|
+
discard_if = options.delete(:discard_if)
|
114
|
+
discard_if = discard_if.to_proc if discard_if.is_a?(Symbol)
|
115
|
+
managed_association_attributes[association_id][:discard_if] = discard_if
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
has_many_without_association_option(association_id, options, &extension)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end # Associations
|
124
|
+
end # AttributeFu
|
data/test/Rakefile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
|
+
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
|
+
|
4
|
+
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
|
5
|
+
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/testtask'
|
8
|
+
require 'rake/rdoctask'
|
9
|
+
|
10
|
+
require 'tasks/rails'
|