restly 0.0.1.beta.3 → 0.0.1.beta.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -25,13 +25,13 @@ module Restly::Base::Instance::Actions
25
25
 
26
26
  def update
27
27
  run_callbacks :update do
28
- set_response(connection.put path_with_format, body: @request_body, params: params)
28
+ set_response(connection.put path_with_format, body: formatted_for_request, params: params)
29
29
  end
30
30
  end
31
31
 
32
32
  def create
33
33
  run_callbacks :create do
34
- set_response(connection.post path_with_format, body: @request_body, params: params)
34
+ set_response(connection.post path_with_format, body: formatted_for_request, params: params)
35
35
  end
36
36
  end
37
37
 
@@ -1,6 +1,14 @@
1
1
  module Restly::Base::Instance::ErrorHandling
2
2
  extend ActiveSupport::Concern
3
3
 
4
+ def append_error(field, error)
5
+ self.errors.add field.to_sym, error
6
+ if /(?<association>\w+)\.(?<attr>.+)/ =~ field && respond_to_association?(association)
7
+ instance_eval(&association.to_sym).append_error(attr, error)
8
+ end
9
+
10
+ end
11
+
4
12
  private
5
13
 
6
14
  def response_has_errors?(response=self.response)
@@ -20,21 +28,23 @@ module Restly::Base::Instance::ErrorHandling
20
28
  case error
21
29
 
22
30
  when Array
23
- error.each { |e| self.errors.add(name.to_sym, e) }
31
+ error.each { |e| append_error name, e }
24
32
 
25
33
  when String
26
- self.errors.add(name.to_sym, error)
34
+ append_error name, error
27
35
 
28
36
  end
37
+
29
38
  end
30
39
 
31
40
  when Array
32
- response_errors.each do |error|
33
- self.errors.add(:base, error)
34
- end
41
+ response_errors.each { |error| append_error :base, error }
35
42
 
36
43
  when String
37
- self.errors.add(:base, response_errors)
44
+ append_error :base, response_errors
45
+
46
+ when NilClass
47
+ append_error :base, connection.status_string(response.status)
38
48
 
39
49
  end
40
50
 
@@ -4,7 +4,7 @@ module Restly::Base::Instance::Persistence
4
4
  return false unless id
5
5
 
6
6
  begin
7
- @response ||= connection.get(path, force: true)
7
+ @response = connection.get(path, force: true) unless @response.status.to_i < 400
8
8
 
9
9
  rescue OAuth2::Error => e
10
10
  @response = e.response
@@ -12,7 +12,7 @@ module Restly::Base::Instance::Persistence
12
12
  end
13
13
 
14
14
  status = @response.status.to_i
15
- status < 300 && status >= 200
15
+ status < 400 && status >= 200
16
16
 
17
17
  end
18
18
 
@@ -2,47 +2,63 @@ module Restly::Base::Instance::WriteCallbacks
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- before_save :format_request
6
- end
5
+ extend ClassMethods
7
6
 
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
7
+ class_attribute :request_builders
8
+ self.request_builders = []
9
+
10
+ build_request :attributes
20
11
 
21
- def savable_resource
22
- {resource_name => attributes_with_present_values(writable_attributes)}
23
12
  end
24
13
 
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
14
+ def formatted_for_request
15
+
16
+ case format.to_sym
17
+ when :json
18
+ built_for_request.to_json
19
+
20
+ when :xml
21
+ built_for_request.to_xml
22
+
23
+ else
24
+ built_for_request.to_param
25
+
33
26
  end
27
+
34
28
  end
35
29
 
36
- def writable_attributes(attributes=self.attributes)
30
+ def built_for_request(resource_key=resource_name)
31
+
32
+ attributes = request_builders.reduce(HashWithIndifferentAccess.new) do |attributes, builder|
33
+ attributes.merge! builder.is_a?(Symbol) ? send(builder) : instance_eval(&builder)
34
+ end
35
+
37
36
  maa = mass_assignment_authorizer(:default)
38
37
 
39
38
  if maa.is_a? ActiveModel::MassAssignmentSecurity::BlackList
40
- attributes.reject{ |key, val| maa.map(&:to_sym).include?(key.to_sym) }
39
+ attributes.except! *maa.map(&:to_sym)
41
40
 
42
41
  elsif maa.is_a? ActiveModel::MassAssignmentSecurity::WhiteList
43
- attributes.select{ |key, val| maa.map(&:to_sym).include?(key.to_sym) }
42
+ attributes.slice! *maa.map(&:to_sym)
43
+
44
+ end
45
+
46
+ if resource_key
47
+ { resource_key.to_sym => attributes.select { |k,v| v.present? } }
48
+ else
49
+ attributes.select { |k,v| v.present? }
50
+ end
44
51
 
52
+ end
53
+
54
+ module ClassMethods
55
+
56
+ private
57
+
58
+ def build_request(symbol=nil, &block)
59
+ self.request_builders << symbol || block
45
60
  end
61
+
46
62
  end
47
63
 
48
64
 
@@ -24,6 +24,7 @@ module Restly::Base::Resource::Finders
24
24
  end
25
25
 
26
26
  def instance_from_response(response)
27
+ raise Restly::Error::RecordNotFound, "Could not find a #{name} at the specified path." unless response.status < 400
27
28
  new(nil, response: response, connection: connection)
28
29
  end
29
30
 
@@ -10,12 +10,12 @@ class Restly::Base::Resource::Specification::Fields < Restly::Proxies::Base
10
10
  end
11
11
 
12
12
  def - field
13
- @removed << field
13
+ @removed += field
14
14
  replace(__getobj__.send __method__, field)
15
15
  end
16
16
 
17
17
  def + field
18
- @added << field
18
+ @added += field
19
19
  replace(__getobj__.send __method__, field)
20
20
  end
21
21
 
@@ -38,7 +38,7 @@ class Restly::Base::Resource::Specification::Fields < Restly::Proxies::Base
38
38
 
39
39
  def reload_specification!
40
40
  from_spec = spec[:attributes] || []
41
- fields = (from_spec - @removed.to_a) + @added.to_a
41
+ fields = (from_spec.map(&:to_sym) - @removed.map(&:to_sym)) + @added.map(&:to_sym)
42
42
  __setobj__ Restly::Base::Fields::FieldSet.new(spec.model, fields)
43
43
  end
44
44
 
@@ -61,10 +61,15 @@ class Restly::Connection < OAuth2::AccessToken
61
61
  }
62
62
  end
63
63
 
64
+ def status_string(int)
65
+ Rack::Utils::HTTP_STATUS_CODES[int.to_i]
66
+ end
67
+
64
68
  alias_method :forced_request, :request
65
69
 
66
70
  def request(verb, path, opts={}, &block)
67
- path = [base_path.gsub(/\/?$/, ''), path.gsub(/^\/?/, '')].join('/')
71
+
72
+ path = [base_path.gsub(/\/?$/, ''), path.gsub(/^\/?/, '')].join('/') unless /#{base_path}/.match path
68
73
 
69
74
  if cache && !opts[:force]
70
75
  request_log("Restly::CacheRequest", path, verb) do
@@ -77,6 +82,8 @@ class Restly::Connection < OAuth2::AccessToken
77
82
  end
78
83
  end
79
84
 
85
+ private
86
+
80
87
  def id_from_path(path)
81
88
  capture = path.match /(?<id>[0-9])\.\w*$/
82
89
  capture[:id] if capture
@@ -123,7 +130,7 @@ class Restly::Connection < OAuth2::AccessToken
123
130
 
124
131
  if response.status >= 500
125
132
  site = URI.parse(client.site)
126
- formatted_path = ["#{site.scheme}://#{site.host}", "#{site.port}", path].join
133
+ formatted_path = ["#{site.scheme}://#{site.host}", ":#{site.port}", path].join
127
134
  raise Restly::Error::ConnectionError, "#{response.status}: #{status_string(response.status)}\nurl: #{formatted_path}"
128
135
  end
129
136
 
@@ -142,8 +149,4 @@ class Restly::Connection < OAuth2::AccessToken
142
149
  ActiveSupport::Notifications.instrument("cache.restly", key: key, name: name, color: color, &block)
143
150
  end
144
151
 
145
- def status_string(int)
146
- Rack::Utils::HTTP_STATUS_CODES[int.to_i]
147
- end
148
-
149
152
  end
@@ -4,7 +4,7 @@ module Restly::EmbeddedAssociations::ClassMethods
4
4
 
5
5
  # Embeds One
6
6
  def embeds_resource(name, options = {})
7
- exclude_field(name) if ancestors.include?(Restly::Base)
7
+ exclude_field(name)
8
8
  self.resource_associations[name] = association = Restly::EmbeddedAssociations::EmbedsOne.new(self, name, options)
9
9
 
10
10
  define_method name do |options={}|
@@ -20,7 +20,7 @@ module Restly::EmbeddedAssociations::ClassMethods
20
20
 
21
21
  # Embeds Many
22
22
  def embeds_resources(name, options = {})
23
- exclude_field(name) if ancestors.include?(Restly::Base)
23
+ exclude_field(name)
24
24
  self.resource_associations[name] = association = Restly::EmbeddedAssociations::EmbedsMany.new(self, name, options)
25
25
 
26
26
  define_method name do |options={}|
@@ -43,6 +43,13 @@ module Restly::EmbeddedAssociations::ClassMethods
43
43
  define_method "#{name}=" do |value|
44
44
  set_association name, value
45
45
  end
46
+
47
+ [:save, :delete, :destroy].each do |method|
48
+ define_method method do
49
+ raise NotImplemented, "Embedded actions have not been implemented."
50
+ end
51
+ end
52
+
46
53
  end
47
54
 
48
55
  end
@@ -6,23 +6,28 @@ module Restly::NestedAttributes
6
6
 
7
7
  included do
8
8
  class_attribute :resource_nested_attributes_options, :instance_writer => false
9
- self.resource_nested_attributes_options = {}
9
+ self.resource_nested_attributes_options = HashWithIndifferentAccess.new
10
10
 
11
11
  inherited do
12
12
  self.resource_nested_attributes_options = resource_nested_attributes_options.dup
13
13
  end
14
14
 
15
+ if ancestors.include?(Restly::Base)
16
+ include Restly::Base::Instance::WriteCallbacks
17
+ build_request :nested_attributes_for_request
18
+ else
19
+ before_save :save_nested_attributes_associations
20
+ end
21
+
15
22
  end
16
23
 
17
24
  private
18
25
 
19
26
  # One To One Association
20
- def assign_nested_attributes_for_one_to_one_resource_association( association_name, attributes )
27
+ def assign_nested_attributes_for_one_to_one_resource_association( association_name, attributes={} )
21
28
 
22
- association_attributes[association_name] = attributes.delete("#{association_name}_attributes") || {}
23
- associated_instance = send(association_name) ||
24
- self.class.reflect_on_resource_association(association_name).build(self)
25
- associated_instance.attributes = association_attributes
29
+ associated_instance = send(association_name) || self.class.reflect_on_resource_association(association_name).build(self)
30
+ associated_instance.attributes = attributes
26
31
 
27
32
  end
28
33
 
@@ -57,15 +62,40 @@ module Restly::NestedAttributes
57
62
 
58
63
  end
59
64
 
60
- def set_nested_attributes_for_save
61
- @attributes = @attributes.reduce(HashWithIndifferentAccess.new) do |hash, (key, v)|
62
- options = resource_nested_attributes_options[key.to_sym]
63
- key = [ options[:write_prefix], key, options[:write_suffix] ].compact.join('_') if options.present?
64
- hash[key] = v
65
+ def nested_attributes_for_request
66
+ resource_nested_attributes_options.reduce(HashWithIndifferentAccess.new) do |hash, (association_name, options)|
67
+ association = self.class.reflect_on_resource_association association_name.to_sym
68
+ next unless association.options[:autosave]
69
+
70
+ associated_object = instance_eval(&association_name.to_sym)
71
+ built_for_request = if associated_object.is_a? Array
72
+ associated_object.map { |instance| instance.built_for_request(nil) if instance.changed? }.compact
73
+ else
74
+ associated_object.built_for_request(nil) if associated_object.changed?
75
+ end
76
+
77
+ key = [ options[:write_prefix], association_name, options[:write_suffix] ].compact.join('_')
78
+ hash[key] = built_for_request if built_for_request.present?
65
79
  hash
66
80
  end
67
81
  end
68
82
 
83
+ def save_nested_attributes_associations
84
+ resource_nested_attributes_options.each do |association_name, options|
85
+ association = self.class.reflect_on_resource_association association_name.to_sym
86
+ next unless association.options[:autosave]
87
+
88
+ associated_object = instance_eval(&association_name.to_sym)
89
+ if associated_object.is_a? Array
90
+ associated_object.each do |instance|
91
+ instance.save && instance.changed?
92
+ end
93
+ else
94
+ associated_object.save if associated_object.changed?
95
+ end
96
+ end
97
+ end
98
+
69
99
  def nested_attribute_missing(m, *args)
70
100
  if !!(matched = ATTR_MATCHER.match m) && (options = resource_nested_attributes_options[(attr = matched[:attr])])
71
101
  send( "assign_nested_attributes_for_#{options[:association_type]}_resource_association", attr, *args )
@@ -80,6 +110,14 @@ module Restly::NestedAttributes
80
110
  super
81
111
  end
82
112
 
113
+ def respond_to_nested_attributes?(m)
114
+ (matched = ATTR_MATCHER.match m) && resource_nested_attributes_options.has_key?(matched[:attr].to_sym)
115
+ end
116
+
117
+ def respond_to?(m, include_private = false)
118
+ respond_to_nested_attributes?(m) || super
119
+ end
120
+
83
121
  module ClassMethods
84
122
  REJECT_ALL_BLANK_PROC = proc { |attributes| attributes.all? { |key, value| key == '_destroy' || value.blank? } }
85
123
 
@@ -89,8 +127,6 @@ module Restly::NestedAttributes
89
127
  options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only, :write_prefix, :write_suffix)
90
128
  options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank
91
129
 
92
- before_save :set_nested_attributes_for_save
93
-
94
130
  attr_names.each do |association_name|
95
131
 
96
132
  if ( reflection = reflect_on_resource_association(association_name) )
@@ -1,3 +1,3 @@
1
1
  module Restly
2
- VERSION = "0.0.1.beta.3"
2
+ VERSION = "0.0.1.beta.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: restly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1.beta.3
4
+ version: 0.0.1.beta.4
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-11-22 00:00:00.000000000 Z
12
+ date: 2012-11-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: oauth2