restly 0.0.1.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/.gitignore +18 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +29 -0
  5. data/Rakefile +1 -0
  6. data/lib/restly/associations/base.rb +35 -0
  7. data/lib/restly/associations/belongs_to.rb +15 -0
  8. data/lib/restly/associations/builder.rb +30 -0
  9. data/lib/restly/associations/embeddable_resources/embeds_many.rb +7 -0
  10. data/lib/restly/associations/embeddable_resources/embeds_one.rb +7 -0
  11. data/lib/restly/associations/embeddable_resources.rb +28 -0
  12. data/lib/restly/associations/has_many.rb +23 -0
  13. data/lib/restly/associations/has_one.rb +23 -0
  14. data/lib/restly/associations.rb +90 -0
  15. data/lib/restly/base/fields.rb +79 -0
  16. data/lib/restly/base/generic_methods.rb +24 -0
  17. data/lib/restly/base/includes.rb +53 -0
  18. data/lib/restly/base/instance/actions.rb +35 -0
  19. data/lib/restly/base/instance/attributes.rb +88 -0
  20. data/lib/restly/base/instance/persistence.rb +20 -0
  21. data/lib/restly/base/instance.rb +76 -0
  22. data/lib/restly/base/mass_assignment_security.rb +19 -0
  23. data/lib/restly/base/resource/finders.rb +32 -0
  24. data/lib/restly/base/resource.rb +19 -0
  25. data/lib/restly/base/write_callbacks.rb +37 -0
  26. data/lib/restly/base.rb +82 -0
  27. data/lib/restly/client.rb +34 -0
  28. data/lib/restly/collection/pagination.rb +36 -0
  29. data/lib/restly/collection.rb +47 -0
  30. data/lib/restly/configuration.rb +43 -0
  31. data/lib/restly/connection.rb +99 -0
  32. data/lib/restly/controller_methods.rb +16 -0
  33. data/lib/restly/error.rb +30 -0
  34. data/lib/restly/middleware.rb +15 -0
  35. data/lib/restly/nested_attributes.rb +97 -0
  36. data/lib/restly/proxies/associations/collection.rb +22 -0
  37. data/lib/restly/proxies/associations/instance.rb +11 -0
  38. data/lib/restly/proxies/auth.rb +9 -0
  39. data/lib/restly/proxies/base.rb +39 -0
  40. data/lib/restly/proxies/params.rb +8 -0
  41. data/lib/restly/proxies.rb +18 -0
  42. data/lib/restly/railtie.rb +9 -0
  43. data/lib/restly/thread_local.rb +33 -0
  44. data/lib/restly/version.rb +3 -0
  45. data/lib/restly.rb +24 -0
  46. data/restly.gemspec +28 -0
  47. data/spec/fakewebs.rb +0 -0
  48. data/spec/helper.rb +31 -0
  49. data/spec/restly/base_spec.rb +60 -0
  50. metadata +210 -0
@@ -0,0 +1,76 @@
1
+ module Restly::Base::Instance
2
+ extend ActiveSupport::Autoload
3
+ extend ActiveSupport::Concern
4
+ autoload :Actions
5
+ autoload :Attributes
6
+ autoload :Persistence
7
+
8
+ include Restly::Base::GenericMethods
9
+ include Actions
10
+ include Attributes
11
+ include Persistence
12
+
13
+ included do
14
+ attr_reader :init_options, :response
15
+ delegate :spec, to: :klass
16
+ end
17
+
18
+ def initialize(attributes = nil, options = {})
19
+
20
+ @init_options = options
21
+ @attributes = HashWithIndifferentAccess.new
22
+ @association_cache = {}
23
+ @aggregation_cache = {}
24
+ @attributes_cache = {}
25
+ @previously_changed = {}
26
+ @changed_attributes = {}
27
+
28
+ run_callbacks :initialize do
29
+
30
+ @readonly = options[:readonly] || false
31
+ set_response options[:response] if options[:response]
32
+ @loaded = options.has_key?(:loaded) ? options[:loaded] : true
33
+ self.attributes = attributes if attributes
34
+ self.connection = options[:connection] if options[:connection].is_a?(OAuth2::AccessToken)
35
+ self.path = if response && response.response.env[:url]
36
+ response.response.env[:url].path.gsub(/\.\w+$/,'')
37
+ elsif respond_to?(:id) && id
38
+ [path, id].join('/')
39
+ else
40
+ klass.path
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ def pry_in
47
+ binding.pry
48
+ end
49
+
50
+ def set_response(response)
51
+ raise Restly::Error::InvalidResponse unless response.is_a? OAuth2::Response
52
+ @response = response
53
+ if response.try(:body)
54
+ set_attributes_from_response
55
+ stub_associations_from_response
56
+ end
57
+ end
58
+
59
+ def connection
60
+ @connection || self.class.connection
61
+ end
62
+
63
+ def connection=(val)
64
+ @connection
65
+ end
66
+
67
+ # Todo: Needed?
68
+ def instance
69
+ self
70
+ end
71
+
72
+ def klass
73
+ self.class
74
+ end
75
+
76
+ end
@@ -0,0 +1,19 @@
1
+ module Restly::Base::MassAssignmentSecurity
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+
6
+ def attr_accessible(*args)
7
+ options = args.dup.extract_options!
8
+ if options[:from_spec]
9
+ before_initialize do
10
+ self._accessible_attributes = spec[:actions].map { |action| action['parameters'] }.flatten
11
+ end
12
+ else
13
+ super(*args)
14
+ end
15
+ end
16
+
17
+ end
18
+
19
+ end
@@ -0,0 +1,32 @@
1
+ module Restly::Base::Resource::Finders
2
+
3
+ def find(*args)
4
+ options = args.extract_options!
5
+
6
+ #params[pagination_options[:params][:page]] = options[:page] if pagination
7
+ instance_from_response connection.get(path_with_format(args.first), params: params)
8
+ end
9
+
10
+ def all
11
+ collection_from_response connection.get(path_with_format, params: params)
12
+ end
13
+
14
+ def create(attributes = nil, options = {})
15
+ instance = self.new(attributes, options)
16
+ instance.save
17
+ end
18
+
19
+ private
20
+
21
+ def collection_from_response(response)
22
+ raise Restly::Error::InvalidResponse unless response.is_a? OAuth2::Response
23
+ Restly::Collection.new self, nil, response: response
24
+ end
25
+
26
+ def instance_from_response(response)
27
+ new(nil, response: response, connection: connection)
28
+ end
29
+
30
+ alias_method :from_response, :instance_from_response
31
+
32
+ end
@@ -0,0 +1,19 @@
1
+ module Restly::Base::Resource
2
+ extend ActiveSupport::Autoload
3
+ autoload :Finders
4
+
5
+ include Restly::Base::GenericMethods
6
+ include Finders
7
+
8
+ # OPTIONS FOR /:path
9
+ # Fetches the spec of a remote resource
10
+ def spec(path=self.path)
11
+ begin
12
+ parsed_response = authorize(client_token).connection.request(:options, path, params: params).parsed
13
+ (parsed_response || {}).with_indifferent_access
14
+ rescue OAuth2::Error
15
+ raise Restly::Error::InvalidSpec, "Unable to load the specification for #{self.class}"
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,37 @@
1
+ module Restly::Base::WriteCallbacks
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_save :format_request
6
+ end
7
+
8
+ private
9
+
10
+ def format_request
11
+ @request_body = case format.to_sym
12
+ when :json
13
+ savable_resource.to_json
14
+ when :xml
15
+ savable_resource.to_xml
16
+ else
17
+ savable_resource.to_param
18
+ end
19
+ end
20
+
21
+ def savable_resource
22
+ {resource_name => attributes_with_present_values}
23
+ end
24
+
25
+ def attributes_with_present_values(attributes=self.attributes)
26
+ attributes.as_json.reduce({}) do |hash, (key, val)|
27
+ if val.is_a?(Hash)
28
+ hash[key] = attributes_with_present_values(val)
29
+ elsif val.present? && key.to_sym != :id
30
+ hash[key] = val
31
+ end
32
+ hash
33
+ end
34
+ end
35
+
36
+
37
+ end
@@ -0,0 +1,82 @@
1
+ module Restly
2
+ class Base
3
+ # Autoload
4
+ extend ActiveSupport::Autoload
5
+ #autoload :Pagination # Todo!
6
+ autoload :Resource
7
+ autoload :Instance
8
+ autoload :Collection
9
+ autoload :GenericMethods
10
+ autoload :Includes
11
+ autoload :WriteCallbacks
12
+ autoload :MassAssignmentSecurity
13
+ autoload :Fields
14
+
15
+ # Thread Local Accessor
16
+ extend Restly::ThreadLocal
17
+
18
+ # Active Model
19
+ extend ActiveModel::Naming
20
+ extend ActiveModel::Callbacks
21
+ extend ActiveModel::Translation
22
+ extend ActiveModel::Callbacks
23
+ include ActiveModel::MassAssignmentSecurity
24
+ include ActiveModel::Conversion
25
+ include ActiveModel::Dirty
26
+ include ActiveModel::Observing
27
+ include ActiveModel::Validations
28
+ include ActiveModel::Serialization
29
+ include ActiveModel::Serializers::JSON
30
+ include ActiveModel::Serializers::Xml
31
+
32
+ # Set Up Callbacks
33
+ define_model_callbacks :create, :save, :delete, :update, :initialize
34
+
35
+ # Actions & Callbacks
36
+ extend Resource
37
+ include Includes
38
+ include Instance
39
+ include Fields
40
+ include MassAssignmentSecurity
41
+ include WriteCallbacks
42
+
43
+ # Relationships
44
+ include Restly::Associations
45
+
46
+ # Set up the Attributes
47
+ thread_local_accessor :current_token
48
+ class_attribute :resource_name,
49
+ :path,
50
+ :include_root_in_json,
51
+ :params,
52
+ :cache,
53
+ :cache_options,
54
+ :client_token
55
+
56
+ self.include_root_in_json = Restly::Configuration.include_root_in_json
57
+ self.cache = Restly::Configuration.cache
58
+ self.cache_options = Restly::Configuration.cache_options
59
+ self.params = {}
60
+ self.current_token = {}
61
+ self.client_token = client.client_credentials.get_token rescue nil
62
+
63
+ # Set Defaults on Inheritance
64
+ inherited do
65
+ field :id
66
+ self.resource_name = name.gsub(/.*::/,'').underscore
67
+ self.path = resource_name.pluralize
68
+ self.params = params.dup
69
+ end
70
+
71
+ # Run Active Support Load Hooks
72
+ ActiveSupport.run_load_hooks(:restly, self)
73
+
74
+ delegate :client, to: :klass
75
+
76
+ # Alias the class for delegation
77
+ def klass
78
+ self.class
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,34 @@
1
+ class Restly::Client < OAuth2::Client
2
+
3
+ attr_accessor :id, :secret, :site
4
+ attr_reader :format
5
+
6
+ def initialize(*args, &block)
7
+ opts = args.extract_options!
8
+ self.id = args[0] || Restly::Configuration.client_id
9
+ self.secret = args[1] || Restly::Configuration.client_secret
10
+ self.site = opts.delete(:site) || Restly::Configuration.site
11
+ self.options = Restly::Configuration.client_options.merge(opts)
12
+ self.ssl = opts.delete(:ssl) || Restly::Configuration.ssl
13
+ self.format = @format = opts.delete(:format) || Restly::Configuration.default_format
14
+ self.options[:connection_build] = block
15
+ end
16
+
17
+ def ssl=(val)
18
+ self.options[:connection_opts][:ssl] = val if val
19
+ end
20
+
21
+ def format=(val)
22
+ self.options[:connection_opts][:headers] = {
23
+ "Accept" => "application/#{format}",
24
+ "Content-Type" => "application/#{format}"
25
+ }
26
+ end
27
+
28
+ Restly::Configuration.client_options.keys.each do |m|
29
+ define_method "#{m}=" do |val|
30
+ self.options[m] = val
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,36 @@
1
+ module Restly::Base::Collection::Pagination
2
+
3
+ def page(num)
4
+ with_params(page: num, per_page: per_page).all.paginate(page: num, per_page: per_page)
5
+ end
6
+
7
+ def current_page
8
+ pagination[:current_page] || pagination[:page]
9
+ end
10
+
11
+ def per_page
12
+ @pagination_opts[:per_page] || pagination[:per_page]
13
+ end
14
+
15
+ def response_per_page
16
+ pagination[:per_page]
17
+ end
18
+
19
+ def total_pages
20
+ pagination[:total_pages]
21
+ end
22
+
23
+ def total_entries
24
+ pagination[:total_entries]
25
+ end
26
+
27
+ private
28
+
29
+ def pagination
30
+ parsed = @response.parsed || {}
31
+ pagination = parsed[:pagination] || parsed
32
+ pagination.select!{ |k,v| /page|current_page|entries|total_entries|per_page|total_pages|total/ =~ k.to_s }
33
+ pagination.with_indifferent_access
34
+ end
35
+
36
+ end
@@ -0,0 +1,47 @@
1
+ class Restly::Collection < Array
2
+ extend ActiveSupport::Autoload
3
+
4
+ delegate :resource_name, :connection, to: :resource
5
+
6
+ autoload :Pagination
7
+
8
+ attr_reader :resource
9
+
10
+ def initialize(resource, array, opts={})
11
+ @resource = resource
12
+ @response = opts[:response]
13
+ array = items_from_response if @response.is_a?(OAuth2::Response)
14
+ super(array)
15
+ end
16
+
17
+ def paginate(opts={})
18
+ @pagination_opts = opts
19
+ collection = self.dup
20
+ collection.extend(Restly::Collection::Pagination)
21
+ return page(opts[:page]) unless opts[:page] == current_page && opts[:per_page] == response_per_page
22
+ collection
23
+ end
24
+
25
+ def <<(instance)
26
+ raise Restly::Error::InvalidObject, "Object is not an instance of #{resource}" unless instance.is_a?(resource)
27
+ super(instance)
28
+ end
29
+
30
+ def serializable_hash(options = nil)
31
+ self.collect do |i|
32
+ i.serializable_hash(options)
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def items_from_response
39
+ parsed = @response.parsed || {}
40
+ parsed = parsed[resource_name.pluralize] if parsed.is_a?(Hash) && parsed[resource_name.pluralize]
41
+ parsed.collect do |instance|
42
+ instance = instance[resource_name] if instance[resource_name]
43
+ resource.new(instance, connection: connection)
44
+ end
45
+ end
46
+
47
+ end
@@ -0,0 +1,43 @@
1
+ module Restly::Configuration
2
+
3
+ def self.config
4
+ defaults = {
5
+ session_key: :access_token,
6
+ load_middleware: true,
7
+ :authorize_url => '/oauth/authorize',
8
+ :token_url => '/oauth/token',
9
+ :token_method => :post,
10
+ :connection_opts => {},
11
+ :max_redirects => 5,
12
+ :raise_errors => true
13
+ }
14
+ defaults.merge(@config || {})
15
+ end
16
+
17
+ def self.client_options
18
+ config.select do |k,v|
19
+ [ :authorize_url,
20
+ :token_url,
21
+ :token_method,
22
+ :connection_opts,
23
+ :max_redirects,
24
+ :raise_errors
25
+ ].include?(k.to_sym)
26
+ end
27
+ end
28
+
29
+ def self.load_config(hash)
30
+ @config = hash
31
+ end
32
+
33
+ def self.method_missing(m, *args, &block)
34
+ config.with_indifferent_access[m]
35
+ end
36
+
37
+ def respond_to_missing?
38
+ true
39
+ end
40
+
41
+ load_config YAML.load_file(File.join(Rails.root, 'config', 'restly.yml'))[Rails.env] if defined? Rails
42
+
43
+ end
@@ -0,0 +1,99 @@
1
+ class Restly::Connection < OAuth2::AccessToken
2
+
3
+ attr_accessor :cache, :cache_options
4
+
5
+ # TODO: Refactor with subclasses that have their own tokenize methods.
6
+ def self.tokenize(client, object)
7
+
8
+ if object.is_a?(Hash)
9
+ from_hash(client, object)
10
+
11
+ elsif object.is_a?(Rack::Request)
12
+ from_rack_request(client, object)
13
+
14
+ elsif object.is_a?(OAuth2::AccessToken)
15
+ from_token_object(client, object)
16
+
17
+ elsif object.is_a?(Restly::Middleware)
18
+
19
+ /(?<token>\w+)$/i =~ object.env['HTTP_AUTHORIZATION']
20
+ return from_rack_request(client, object) if token
21
+ token_hash = object.env['rack.session'][Restly::Configuration.session_key] || {}
22
+ from_hash(client, token_hash)
23
+
24
+ else
25
+ new(client, nil)
26
+
27
+ end
28
+
29
+ end
30
+
31
+ def initialize(client, token, opts={})
32
+ self.cache_options = opts[:cache_options] || {}
33
+ self.cache = opts[:cache]
34
+ super
35
+ end
36
+
37
+ def self.from_hash(client, token_hash)
38
+ super(client, token_hash.dup)
39
+ end
40
+
41
+ def self.from_rack_request(client, rack_request)
42
+ /(?<token>\w+)$/i =~ rack_request.env['HTTP_AUTHORIZATION']
43
+ from_hash(client, { access_token: token })
44
+ end
45
+
46
+ def self.from_token_object(client, token_object)
47
+ from_hash(client, {
48
+ access_token: token_object.token,
49
+ refresh_token: token_object.refresh_token,
50
+ expires_at: token_object.expires_at
51
+ })
52
+ end
53
+
54
+ def to_hash
55
+ {
56
+ access_token: token,
57
+ refresh_token: refresh_token,
58
+ expires_at: expires_at
59
+ }
60
+ end
61
+
62
+ alias_method :forced_request, :request
63
+
64
+ def request(verb, path, opts={}, &block)
65
+ if cache && !opts[:force]
66
+ cached_request(verb, path, opts, &block)
67
+ else
68
+ forced_request(verb, path, opts, &block)
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ def cached_request(verb, path, opts={}, &block)
75
+ options_hash = { verb: verb, token: token, opts: opts, block: block }
76
+ options_packed = [Marshal.dump(options_hash)].pack('m')
77
+ options_hex = Digest::MD5.hexdigest(options_packed)
78
+ cache_key = [path.parameterize, options_hex].join('_')
79
+
80
+ # Force a cache miss for all methods except get
81
+ cache_options[:force] = true unless [:get, :options].include?(verb)
82
+
83
+ # Set the response
84
+ response = Rails.cache.fetch cache_key, cache_options.symbolize_keys do
85
+
86
+ Rails.cache.delete_matched("#{path.parameterize}*") if ![:get, :options].include?(verb)
87
+ opts.merge!({force: true})
88
+ request(verb, path, opts, &block)
89
+
90
+ end
91
+
92
+ # Clear the cache if there is an error
93
+ Rails.cache.delete(cache_key) and puts "deleted cache for: #{verb} #{path}" if response.error
94
+
95
+ # Return the response
96
+ response
97
+
98
+ end
99
+ end
@@ -0,0 +1,16 @@
1
+ module Restly::ControllerMethods
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+
6
+ def auth_token
7
+ @oauth_token ||= session[Restly::Configuration.session_key].try(:dup) || {}
8
+ end
9
+
10
+ def auth_token=(token_object)
11
+ session[Restly::Configuration.session_key] = Restly::Connection.tokenize(Restly::Base.client, token_object).to_hash
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,30 @@
1
+ module Restly::Error
2
+
3
+ class NotAnRestly < StandardError
4
+ end
5
+
6
+ class RecordNotFound < StandardError
7
+ end
8
+
9
+ class WrongResourceType < StandardError
10
+ end
11
+
12
+ class InvalidParentAssociation < StandardError
13
+ end
14
+
15
+ class InvalidJoinerAssociation < StandardError
16
+ end
17
+
18
+ class InvalidObject < StandardError
19
+ end
20
+
21
+ class InvalidToken < StandardError
22
+ end
23
+
24
+ class InvalidConnection < StandardError
25
+ end
26
+
27
+ class InvalidSpec < StandardError
28
+ end
29
+
30
+ end
@@ -0,0 +1,15 @@
1
+ class Restly::Middleware
2
+
3
+ attr_reader :app, :env
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ @env = env
11
+ Restly::Base.current_token = Restly::Connection.tokenize(Restly::Base.client, self).to_hash
12
+ self.app.call(self.env)
13
+ end
14
+
15
+ end
@@ -0,0 +1,97 @@
1
+ module Restly::NestedAttributes
2
+
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :resource_nested_attributes_options, :instance_writer => false
7
+ self.resource_nested_attributes_options = {}
8
+
9
+ inherited do
10
+ self.resource_nested_attributes_options = resource_nested_attributes_options.dup
11
+ end
12
+
13
+ end
14
+
15
+ # One To One Association
16
+ def assign_nested_attributes_for_one_to_one_resource_association(association_name, attributes, assignment_opts = {})
17
+ options = self.nested_attributes_options[association_name]
18
+ association_attributes = attributes.delete("#{association_name}_attributes") || {}
19
+ associated_instance = send(association_name) || self.class.reflect_on_resource_association(association_name).build
20
+ associated_instance.attributes = association_attributes
21
+ end
22
+
23
+ # Collection Association
24
+ def assign_nested_attributes_for_collection_resource_association(association_name, attributes_collection, assignment_opts = {})
25
+
26
+ unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
27
+ raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
28
+ end
29
+
30
+ if attributes_collection.is_a? Hash
31
+ keys = attributes_collection.keys
32
+ attributes_collection = if keys.include?('id') || keys.include?(:id)
33
+ Array.wrap(attributes_collection)
34
+ else
35
+ attributes_collection.values
36
+ end
37
+ end
38
+
39
+ association = self.class.reflect_on_resource_association(association_name)
40
+ existing_records = send(association_name)
41
+
42
+ attributes_collection.each do |attributes|
43
+ attributes = attributes.with_indifferent_access
44
+ if attributes[:id].blank?
45
+ send(association_name) << association.build(attributes.except(:id))
46
+ elsif existing_record = existing_records.find{ |record| record.id.to_s == attributes['id'].to_s }
47
+ existing_record.attributes = attributes
48
+ end
49
+ end
50
+
51
+ end
52
+
53
+ def set_nested_attributes_for_save
54
+ @attributes = @attributes.inject(HashWithIndifferentAccess.new) do |hash, (k, v)|
55
+ k = [resource_nested_attributes_options[k.to_sym][:write_prefix], k, resource_nested_attributes_options[k.to_sym][:write_suffix]].compact.join('_') if resource_nested_attributes_options[k.to_sym].present?
56
+ hash[k] = v
57
+ hash
58
+ end
59
+ end
60
+
61
+ module ClassMethods
62
+ REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
63
+
64
+ def accepts_nested_attributes_for_resource(*attr_names)
65
+ options = { :allow_destroy => false, :update_only => false, :write_prefix => nil, :write_suffix => 'attributes' }
66
+ options.update(attr_names.extract_options!)
67
+ options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only, :write_prefix, :write_suffix)
68
+ options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
69
+
70
+ before_save :set_nested_attributes_for_save
71
+
72
+ attr_names.each do |association_name|
73
+ if reflection = reflect_on_resource_association(association_name)
74
+ reflection.options[:autosave] = true
75
+
76
+ resource_nested_attributes_options = self.resource_nested_attributes_options.dup
77
+ resource_nested_attributes_options[association_name.to_sym] = options
78
+ self.resource_nested_attributes_options = resource_nested_attributes_options
79
+
80
+ type = (reflection.collection? ? :collection : :one_to_one)
81
+
82
+ if method_defined?("#{association_name}_attributes=")
83
+ remove_method("#{association_name}_attributes=")
84
+ end
85
+
86
+ define_method "#{association_name}_attributes=" do |attributes|
87
+ send("assign_nested_attributes_for_#{type}_resource_association", association_name.to_sym, attributes, mass_assignment_options)
88
+ end
89
+
90
+ else
91
+ raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ end