restly 0.0.1.alpha.1

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.
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