restly 0.0.1.alpha.16 → 0.0.1.alpha.18
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -112
- data/lib/restly.rb +26 -1
- data/lib/restly/associations.rb +25 -12
- data/lib/restly/associations/base/conditionals.rb +2 -2
- data/lib/restly/base.rb +1 -0
- data/lib/restly/base/fields.rb +21 -17
- data/lib/restly/base/includes.rb +7 -2
- data/lib/restly/base/instance.rb +31 -12
- data/lib/restly/base/instance/actions.rb +2 -2
- data/lib/restly/base/instance/attributes.rb +46 -40
- data/lib/restly/base/instance/comparable.rb +13 -0
- data/lib/restly/base/instance/error_handling.rb +60 -0
- data/lib/restly/base/instance/persistence.rb +1 -1
- data/lib/restly/base/resource/specification/fields.rb +2 -2
- data/lib/restly/base/resource/specification/mass_assignment_security.rb +1 -1
- data/lib/restly/client.rb +20 -7
- data/lib/restly/collection.rb +48 -12
- data/lib/restly/collection/error_handling.rb +17 -0
- data/lib/restly/connection.rb +55 -11
- data/lib/restly/error.rb +5 -9
- data/lib/restly/middleware.rb +15 -2
- data/lib/restly/nested_attributes.rb +29 -23
- data/lib/restly/notifications.rb +65 -0
- data/lib/restly/version.rb +1 -1
- data/spec/restly/active_model_lint_spec.rb +11 -0
- data/spec/restly/associations/base/builders_spec.rb +8 -0
- data/spec/restly/associations/base/conditionals_spec.rb +8 -0
- data/spec/restly/associations/base/loaders_spec.rb +8 -0
- data/spec/restly/associations/base/modifiers_spec.rb +8 -0
- data/spec/restly/associations/base/stubs_spec.rb +8 -0
- data/spec/restly/associations/class_methods.rb +0 -0
- data/spec/restly/base/instance/comparable_spec.rb +8 -0
- metadata +22 -2
@@ -0,0 +1,13 @@
|
|
1
|
+
module Restly::Base::Instance::Comparable
|
2
|
+
|
3
|
+
def ==(object)
|
4
|
+
reload!
|
5
|
+
object.reload!
|
6
|
+
|
7
|
+
this_object = Marshal.dump attributes.except(:id, :created_at, :updated_at)
|
8
|
+
other_object = Marshal.dump object.attributes.except(:id, :created_at, :updated_at)
|
9
|
+
|
10
|
+
Marshal.dump(this_object) == Marshal.dump(other_object) && self.class == object.class
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Restly::Base::Instance::ErrorHandling
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def response_has_errors?(response=self.response)
|
7
|
+
@response.status >= 400 ||
|
8
|
+
(parsed_response(response).is_a?(Hash) &&
|
9
|
+
(parsed_response(response)[:errors] || parsed_response(response)[:error]))
|
10
|
+
end
|
11
|
+
|
12
|
+
def set_errors_from_response(response = self.response)
|
13
|
+
|
14
|
+
response_errors = parsed_response(response)[:errors] || parsed_response(response)[:error]
|
15
|
+
|
16
|
+
case response_errors
|
17
|
+
|
18
|
+
when Hash
|
19
|
+
response_errors.each do |name, error|
|
20
|
+
case error
|
21
|
+
|
22
|
+
when Array
|
23
|
+
error.each { |e| self.errors.add(name.to_sym, e) }
|
24
|
+
|
25
|
+
when String
|
26
|
+
self.errors.add(name.to_sym, error)
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
when Array
|
32
|
+
response_errors.each do |error|
|
33
|
+
self.errors.add(:base, error)
|
34
|
+
end
|
35
|
+
|
36
|
+
when String
|
37
|
+
self.errors.add(:base, response_errors)
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
self.errors
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_attribute_for_validation(attr)
|
45
|
+
send attr
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
|
50
|
+
def human_attribute_name(attr, options = {})
|
51
|
+
attr.to_s.humanize
|
52
|
+
end
|
53
|
+
|
54
|
+
def lookup_ancestors
|
55
|
+
[self]
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -25,7 +25,7 @@ module Restly::Base::Instance::Persistence
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def reload!
|
28
|
-
raise Restly::Error::MissingId, "Cannot reload #{resource_name}, either it hasn't been created or it is missing an ID." unless
|
28
|
+
raise Restly::Error::MissingId, "Cannot reload #{resource_name}, either it hasn't been created or it is missing an ID." unless new_record?
|
29
29
|
set_attributes_from_response connection.get(path_with_format, force: true)
|
30
30
|
@loaded = true
|
31
31
|
self
|
@@ -6,7 +6,7 @@ class Restly::Base::Resource::Specification::Fields < Restly::Proxies::Base
|
|
6
6
|
@spec = spec
|
7
7
|
@removed = Set.new
|
8
8
|
@added = Set.new
|
9
|
-
super Restly::Base::Fields::FieldSet.new
|
9
|
+
super Restly::Base::Fields::FieldSet.new(spec.model)
|
10
10
|
end
|
11
11
|
|
12
12
|
def -(field)
|
@@ -34,7 +34,7 @@ class Restly::Base::Resource::Specification::Fields < Restly::Proxies::Base
|
|
34
34
|
def reload_specification!
|
35
35
|
from_spec = spec[:attributes] || []
|
36
36
|
fields = (from_spec - @removed.to_a) + @added.to_a
|
37
|
-
__setobj__ Restly::Base::Fields::FieldSet.new(fields)
|
37
|
+
__setobj__ Restly::Base::Fields::FieldSet.new(spec.model, fields)
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|
@@ -45,7 +45,7 @@ module Restly::Base::Resource::Specification::MassAssignmentSecurity
|
|
45
45
|
|
46
46
|
def reload_specification!
|
47
47
|
accepts = spec.actions.map { |action| action['accepts_parameters'] }.flatten if spec.actions.present?
|
48
|
-
__setobj__ ActiveModel::MassAssignmentSecurity::
|
48
|
+
__setobj__ ActiveModel::MassAssignmentSecurity::WhiteList.new accepts
|
49
49
|
end
|
50
50
|
|
51
51
|
end
|
data/lib/restly/client.rb
CHANGED
@@ -1,17 +1,30 @@
|
|
1
1
|
class Restly::Client < OAuth2::Client
|
2
2
|
|
3
3
|
attr_accessor :id, :secret, :site
|
4
|
-
attr_reader :format
|
4
|
+
attr_reader :format, :resource
|
5
5
|
|
6
6
|
def initialize(*args, &block)
|
7
7
|
opts = args.extract_options!
|
8
|
-
|
9
|
-
|
10
|
-
self.
|
11
|
-
self.
|
12
|
-
self.
|
13
|
-
self.
|
8
|
+
opts.merge!(raise_errors: false)
|
9
|
+
|
10
|
+
self.resource = opts.delete(:resource) if opts[:resource]
|
11
|
+
self.id = args[0] || Restly::Configuration.oauth_options[:client_id]
|
12
|
+
self.secret = args[1] || Restly::Configuration.oauth_options[:client_secret]
|
13
|
+
self.site = opts.delete(:site) || Restly::Configuration.site
|
14
|
+
self.options = Restly::Configuration.client_options.merge(opts)
|
15
|
+
self.ssl = opts.delete(:ssl) || Restly::Configuration.ssl
|
16
|
+
self.format = @format = opts.delete(:format) || Restly::Configuration.default_format
|
14
17
|
self.options[:connection_build] ||= block
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
def resource=(resource)
|
22
|
+
raise InvalidObject, "Resource must be a descendant of Restly::Base" unless resource.ancestors.include?(Restly::Base)
|
23
|
+
@resource = resource
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource_name
|
27
|
+
resource.name.parameterize
|
15
28
|
end
|
16
29
|
|
17
30
|
def ssl=(val)
|
data/lib/restly/collection.rb
CHANGED
@@ -1,21 +1,28 @@
|
|
1
1
|
class Restly::Collection < Array
|
2
2
|
extend ActiveSupport::Autoload
|
3
3
|
autoload :Pagination
|
4
|
+
autoload :ErrorHandling
|
4
5
|
|
5
6
|
include Restly::Base::Resource::Finders
|
6
7
|
include Restly::Base::Resource::BatchActions
|
7
8
|
include Restly::Base::GenericMethods
|
9
|
+
include ErrorHandling
|
8
10
|
|
9
11
|
delegate :resource_name, :new, :client, to: :resource
|
10
12
|
|
11
|
-
attr_reader :resource
|
13
|
+
attr_reader :resource, :response
|
12
14
|
|
13
15
|
def initialize(resource, array=[], opts={})
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
ActiveSupport::Notifications.instrument("load_collection.restly", model: resource.name) do
|
17
|
+
@errors = []
|
18
|
+
@resource = resource
|
19
|
+
@connection
|
20
|
+
if opts[:response]
|
21
|
+
set_response(opts[:response])
|
22
|
+
else
|
23
|
+
replace array
|
24
|
+
end
|
25
|
+
end
|
19
26
|
end
|
20
27
|
|
21
28
|
[:path, :connection, :params].each do |attr|
|
@@ -52,24 +59,41 @@ class Restly::Collection < Array
|
|
52
59
|
super(instance)
|
53
60
|
end
|
54
61
|
|
62
|
+
def replace(array)
|
63
|
+
array.each do |instance|
|
64
|
+
raise Restly::Error::InvalidObject, "Object is not an instance of #{resource}" unless accepts?(instance)
|
65
|
+
end
|
66
|
+
super
|
67
|
+
end
|
68
|
+
|
55
69
|
def reload!
|
56
70
|
replace collection_from_response(connection.get path)
|
57
71
|
end
|
58
72
|
|
59
73
|
private
|
60
74
|
|
75
|
+
def set_response(response)
|
76
|
+
raise Restly::Error::InvalidResponse unless response.is_a? OAuth2::Response
|
77
|
+
@response = response
|
78
|
+
if response.try(:body)
|
79
|
+
if response_has_errors?(response)
|
80
|
+
set_errors_from_response
|
81
|
+
else
|
82
|
+
set_items_from_response
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
61
87
|
def serializable_hash(options = nil)
|
62
|
-
self.
|
88
|
+
self.map do |i|
|
63
89
|
i.serializable_hash(options)
|
64
90
|
end
|
65
91
|
end
|
66
92
|
|
67
|
-
def
|
68
|
-
|
69
|
-
parsed = parsed[resource_name.pluralize] if parsed.is_a?(Hash) && parsed[resource_name.pluralize]
|
70
|
-
parsed.collect do |instance|
|
93
|
+
def set_items_from_response(response=self.response)
|
94
|
+
parsed_response(response).reduce(self) do |collection, instance|
|
71
95
|
instance = instance[resource_name] if instance[resource_name]
|
72
|
-
resource.new(instance, connection: connection)
|
96
|
+
collection << resource.new(instance, connection: connection, loaded: false)
|
73
97
|
end
|
74
98
|
end
|
75
99
|
|
@@ -77,4 +101,16 @@ class Restly::Collection < Array
|
|
77
101
|
instance.class.name == resource.name
|
78
102
|
end
|
79
103
|
|
104
|
+
def parsed_response(response=self.response)
|
105
|
+
return {} unless response
|
106
|
+
parsed = response.parsed || {}
|
107
|
+
if parsed.is_a?(Hash) && parsed[resource_name.pluralize]
|
108
|
+
parsed[resource_name.pluralize]
|
109
|
+
elsif parsed.is_a?(Hash)
|
110
|
+
parsed.with_indifferent_access
|
111
|
+
else
|
112
|
+
parsed
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
80
116
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Restly::Collection::ErrorHandling
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def response_has_errors?(response=self.response)
|
5
|
+
@response.status >= 400 ||
|
6
|
+
(parsed_response(response).is_a?(Hash) &&
|
7
|
+
(parsed_response(response)[:errors] || parsed_response(response)[:error]))
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_errors_from_response(response = self.response)
|
11
|
+
if (error = parsed_response(response)[:errors] || parsed_response(response)[:error])
|
12
|
+
@errors << error
|
13
|
+
end
|
14
|
+
replace []
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/restly/connection.rb
CHANGED
@@ -2,6 +2,8 @@ class Restly::Connection < OAuth2::AccessToken
|
|
2
2
|
|
3
3
|
attr_accessor :cache, :cache_options
|
4
4
|
|
5
|
+
delegate :resource, :resource_name, to: :client
|
6
|
+
|
5
7
|
# TODO: Refactor with subclasses that have their own tokenize methods.
|
6
8
|
def self.tokenize(client, object)
|
7
9
|
|
@@ -63,37 +65,79 @@ class Restly::Connection < OAuth2::AccessToken
|
|
63
65
|
|
64
66
|
def request(verb, path, opts={}, &block)
|
65
67
|
if cache && !opts[:force]
|
66
|
-
|
68
|
+
request_log("Restly::CacheRequest", path, verb) do
|
69
|
+
cached_request(verb, path, opts, &block)
|
70
|
+
end
|
67
71
|
else
|
68
|
-
|
72
|
+
request_log("Restly::Request", path, verb) do
|
73
|
+
forced_request(verb, path, opts, &block)
|
74
|
+
end
|
69
75
|
end
|
70
76
|
end
|
71
77
|
|
72
|
-
|
78
|
+
def id_from_path(path)
|
79
|
+
capture = path.match /(?<id>[0-9])\.\w*$/
|
80
|
+
capture[:id] if capture
|
81
|
+
end
|
73
82
|
|
74
83
|
def cached_request(verb, path, opts={}, &block)
|
75
|
-
|
84
|
+
id = id_from_path(path)
|
85
|
+
|
86
|
+
cache_options = self.cache_options.dup
|
87
|
+
options_hash = { path: path, verb: verb, token: token, opts: opts, cache_opts: cache_options, block: block }
|
76
88
|
options_packed = [Marshal.dump(options_hash)].pack('m')
|
77
89
|
options_hex = Digest::MD5.hexdigest(options_packed)
|
78
|
-
|
90
|
+
|
91
|
+
# Keys
|
92
|
+
collection_expire_key = [resource_name, "*"].compact.join('_')
|
93
|
+
instance_expire_key = [id, resource_name, "*"].compact.join('_')
|
94
|
+
cache_key = [id, resource_name, options_hex].compact.join('_')
|
95
|
+
|
79
96
|
|
80
97
|
# Force a cache miss for all methods except get
|
81
98
|
cache_options[:force] = true unless [:get, :options].include?(verb)
|
82
99
|
|
83
100
|
# Set the response
|
84
|
-
|
101
|
+
unless verb.to_s.upcase =~ /GET|OPTIONS/
|
85
102
|
|
86
|
-
|
87
|
-
|
88
|
-
|
103
|
+
# Expire Collections
|
104
|
+
cache_log("Restly::CacheExpire", instance_expire_key, :yellow) do
|
105
|
+
Rails.cache.delete_matched(collection_expire_key)
|
106
|
+
end
|
89
107
|
|
108
|
+
# Expire Instances
|
109
|
+
cache_log("Restly::CacheExpire", instance_expire_key, :yellow) do
|
110
|
+
Rails.cache.delete_matched(instance_expire_key)
|
111
|
+
end
|
90
112
|
end
|
91
113
|
|
92
|
-
|
93
|
-
|
114
|
+
response = Rails.cache.fetch cache_key, cache_options.symbolize_keys do
|
115
|
+
cache_log("Restly::CacheMiss", cache_key, :red) do
|
116
|
+
forced_request(verb, path, opts, &block)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
cache_log("Restly::CacheExpire", cache_key, :yellow) { Rails.cache.delete(cache_key) } if response.error
|
121
|
+
|
122
|
+
raise Restly::Error::ConnectionError, "#{response.status}: #{status_string(response.status)}" if response.status >= 500
|
94
123
|
|
95
124
|
# Return the response
|
96
125
|
response
|
97
126
|
|
98
127
|
end
|
128
|
+
|
129
|
+
def request_log(name, path, verb, color=:light_green, &block)
|
130
|
+
site = URI.parse(client.site)
|
131
|
+
formatted_path = ["#{site.scheme}://#{site.host}", path].join("/")
|
132
|
+
ActiveSupport::Notifications.instrument("request.restly", url: formatted_path, method: verb, name: name, color: color, &block)
|
133
|
+
end
|
134
|
+
|
135
|
+
def cache_log(name, key, color=:light_green, &block)
|
136
|
+
ActiveSupport::Notifications.instrument("cache.restly", key: key, name: name, color: color, &block)
|
137
|
+
end
|
138
|
+
|
139
|
+
def status_string(int)
|
140
|
+
Rack::Utils::HTTP_STATUS_CODES[int.to_i]
|
141
|
+
end
|
142
|
+
|
99
143
|
end
|
data/lib/restly/error.rb
CHANGED
@@ -1,26 +1,22 @@
|
|
1
1
|
module Restly::Error
|
2
2
|
|
3
|
-
class StandardError < ::StandardError
|
4
|
-
|
5
|
-
def message
|
6
|
-
defined?(IRB) ? super.red : super
|
7
|
-
end
|
8
|
-
|
9
|
-
end
|
10
|
-
|
11
3
|
errors = %w{
|
12
4
|
RecordNotFound
|
13
5
|
InvalidClient
|
14
6
|
InvalidObject
|
15
7
|
InvalidConnection
|
8
|
+
ConnectionError
|
16
9
|
MissingId
|
17
10
|
InvalidSpec
|
18
11
|
InvalidField
|
12
|
+
InvalidAssociation
|
13
|
+
InvalidAttribute
|
14
|
+
InvalidNestedAttribute
|
19
15
|
AssociationError
|
20
16
|
}
|
21
17
|
|
22
18
|
errors.each do |error|
|
23
|
-
const_set
|
19
|
+
const_set error.to_sym, Class.new(StandardError)
|
24
20
|
end
|
25
21
|
|
26
22
|
end
|
data/lib/restly/middleware.rb
CHANGED
@@ -8,8 +8,21 @@ class Restly::Middleware
|
|
8
8
|
|
9
9
|
def call(env)
|
10
10
|
@env = env
|
11
|
-
|
12
|
-
|
11
|
+
|
12
|
+
Restly::Base.current_token = nil
|
13
|
+
|
14
|
+
token = Restly::Connection.tokenize(Restly::Base.client, self).to_hash
|
15
|
+
|
16
|
+
if token[:access_token].present? && !@env['PATH_INFO'].match(/^\/assets\//)
|
17
|
+
Restly::Base.current_token = token
|
18
|
+
end
|
19
|
+
|
20
|
+
self.app.call(env)
|
21
|
+
|
22
|
+
ensure
|
23
|
+
|
24
|
+
Restly::Base.current_token = nil
|
25
|
+
|
13
26
|
end
|
14
27
|
|
15
28
|
end
|
@@ -15,19 +15,21 @@ module Restly::NestedAttributes
|
|
15
15
|
private
|
16
16
|
|
17
17
|
# One To One Association
|
18
|
-
def assign_nested_attributes_for_one_to_one_resource_association(association_name, attributes
|
19
|
-
|
18
|
+
def assign_nested_attributes_for_one_to_one_resource_association( association_name, attributes )
|
19
|
+
|
20
20
|
association_attributes[association_name] = attributes.delete("#{association_name}_attributes") || {}
|
21
21
|
associated_instance = send(association_name) ||
|
22
22
|
self.class.reflect_on_resource_association(association_name).build(self)
|
23
23
|
associated_instance.attributes = association_attributes
|
24
|
+
|
24
25
|
end
|
25
26
|
|
26
27
|
# Collection Association
|
27
|
-
def assign_nested_attributes_for_collection_resource_association(association_name, attributes_collection
|
28
|
+
def assign_nested_attributes_for_collection_resource_association(association_name, attributes_collection)
|
28
29
|
|
29
30
|
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
|
30
|
-
raise ArgumentError,
|
31
|
+
raise ArgumentError,
|
32
|
+
"Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
|
31
33
|
end
|
32
34
|
|
33
35
|
if attributes_collection.is_a? Hash
|
@@ -46,7 +48,7 @@ module Restly::NestedAttributes
|
|
46
48
|
attributes = attributes.with_indifferent_access
|
47
49
|
if attributes[:id].blank?
|
48
50
|
send(association_name) << association.build(self, attributes.except(:id))
|
49
|
-
elsif existing_record = existing_records.find{ |record| record.id.to_s == attributes['id'].to_s }
|
51
|
+
elsif (existing_record = existing_records.find{ |record| record.id.to_s == attributes['id'].to_s })
|
50
52
|
existing_record.attributes = attributes
|
51
53
|
end
|
52
54
|
end
|
@@ -54,13 +56,28 @@ module Restly::NestedAttributes
|
|
54
56
|
end
|
55
57
|
|
56
58
|
def set_nested_attributes_for_save
|
57
|
-
@attributes = @attributes.
|
58
|
-
|
59
|
-
|
59
|
+
@attributes = @attributes.reduce(HashWithIndifferentAccess.new) do |hash, (key, v)|
|
60
|
+
options = resource_nested_attributes_options[key.to_sym]
|
61
|
+
key = [ options[:write_prefix], key, options[:write_suffix] ].compact.join('_') if options.present?
|
62
|
+
hash[key] = v
|
60
63
|
hash
|
61
64
|
end
|
62
65
|
end
|
63
66
|
|
67
|
+
def nested_attribute_missing(m, *args)
|
68
|
+
if !!(/(?<attr>\w+)_attributes=$/ =~ m.to_s) && (options = resource_nested_attributes_options[attr])
|
69
|
+
send( "assign_nested_attributes_for_#{options[:association_type]}_resource_association", attr, *args )
|
70
|
+
else
|
71
|
+
raise Restly::Error::InvalidNestedAttribute, "Nested Attribute does not exist!"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(m, *args, &block)
|
76
|
+
nested_attribute_missing(m, *args)
|
77
|
+
rescue Restly::Error::InvalidNestedAttribute
|
78
|
+
super
|
79
|
+
end
|
80
|
+
|
64
81
|
module ClassMethods
|
65
82
|
REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
|
66
83
|
|
@@ -73,22 +90,11 @@ module Restly::NestedAttributes
|
|
73
90
|
before_save :set_nested_attributes_for_save
|
74
91
|
|
75
92
|
attr_names.each do |association_name|
|
76
|
-
if reflection = reflect_on_resource_association(association_name)
|
77
|
-
reflection.options[:autosave] = true
|
78
|
-
|
79
|
-
resource_nested_attributes_options = self.resource_nested_attributes_options.dup
|
80
|
-
resource_nested_attributes_options[association_name.to_sym] = options
|
81
|
-
self.resource_nested_attributes_options = resource_nested_attributes_options
|
82
93
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
|
89
|
-
define_method "#{association_name}_attributes=" do |attributes|
|
90
|
-
send("assign_nested_attributes_for_#{type}_resource_association", association_name.to_sym, attributes, mass_assignment_options)
|
91
|
-
end
|
94
|
+
if ( reflection = reflect_on_resource_association(association_name) )
|
95
|
+
reflection.options[:autosave] = true
|
96
|
+
options[:association_type] = (reflection.collection? ? :collection : :one_to_one)
|
97
|
+
self.resource_nested_attributes_options[association_name.to_sym] = options
|
92
98
|
|
93
99
|
else
|
94
100
|
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
|