her 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,32 +3,29 @@ module Her
3
3
  module Associations
4
4
  class HasOneAssociation < Association
5
5
  # @private
6
- def self.attach(klass, name, attrs)
7
- attrs = {
6
+ def self.attach(klass, name, opts)
7
+ opts = {
8
8
  :class_name => name.to_s.classify,
9
9
  :name => name,
10
10
  :data_key => name,
11
+ :default => nil,
11
12
  :path => "/#{name}"
12
- }.merge(attrs)
13
- klass.associations[:has_one] << attrs
13
+ }.merge(opts)
14
+ klass.associations[:has_one] << opts
14
15
 
15
16
  klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
16
17
  def #{name}
17
18
  cached_name = :"@_her_association_#{name}"
18
19
 
19
20
  cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
20
- cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasOneAssociation.new(self, #{attrs.inspect}))
21
+ cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasOneAssociation.new(self, #{opts.inspect}))
21
22
  end
22
23
  RUBY
23
24
  end
24
25
 
25
26
  # @private
26
- def self.parse(association, klass, data)
27
- data_key = association[:data_key]
28
- return {} unless data[data_key]
29
-
30
- klass = klass.her_nearby_class(association[:class_name])
31
- { association[:name] => klass.new(data[data_key]) }
27
+ def self.parse(*args)
28
+ parse_single(*args)
32
29
  end
33
30
 
34
31
  # Initialize a new object with a foreign key to the parent
@@ -71,30 +68,9 @@ module Her
71
68
  resource
72
69
  end
73
70
 
74
- # @private
75
- def fetch
76
- return nil if @parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @query_attrs.empty?
77
-
78
- if @parent.attributes[@name].blank? || @query_attrs.any?
79
- path = begin
80
- @parent.request_path(@query_attrs)
81
- rescue Her::Errors::PathError
82
- return nil
83
- end
84
-
85
- @klass.get_resource("#{path}#{@opts[:path]}", @query_attrs)
86
- else
87
- @parent.attributes[@name]
88
- end
89
- end
90
-
91
71
  # @private
92
72
  def assign_nested_attributes(attributes)
93
- if @parent.attributes[@name].blank?
94
- @parent.attributes[@name] = @klass.new(@klass.parse(attributes))
95
- else
96
- @parent.attributes[@name].assign_attributes(attributes)
97
- end
73
+ assign_single_nested_attributes(attributes)
98
74
  end
99
75
  end
100
76
  end
@@ -3,7 +3,6 @@ module Her
3
3
  # This module handles all methods related to model attributes
4
4
  module Attributes
5
5
  extend ActiveSupport::Concern
6
- attr_accessor :attributes
7
6
 
8
7
  # Initialize a new object with data
9
8
  #
@@ -63,7 +62,7 @@ module Her
63
62
  #
64
63
  # @private
65
64
  def method_missing(method, *args, &blk)
66
- if method.to_s =~ /[?=]$/ || attributes.include?(method)
65
+ if method.to_s =~ /[?=]$/ || @attributes.include?(method)
67
66
  # Extract the attribute
68
67
  attribute = method.to_s.sub(/[?=]$/, '')
69
68
 
@@ -97,41 +96,48 @@ module Her
97
96
  # user = User.find(1) # => #<User id=1 name="Tobias">
98
97
  # user.assign_attributes(name: "Lindsay")
99
98
  # user.changes # => { :name => ["Tobias", "Lindsay"] }
100
- def assign_attributes(raw_data)
101
- @attributes ||= {}
102
- # Use setter methods first, then translate attributes of associations
103
- # into association instances, then merge the parsed_data into @attributes.
104
- unset_attributes = Her::Model::Attributes.use_setter_methods(self, raw_data)
99
+ def assign_attributes(new_attributes)
100
+ @attributes ||= attributes
101
+ # Use setter methods first
102
+ unset_attributes = Her::Model::Attributes.use_setter_methods(self, new_attributes)
103
+
104
+ # Then translate attributes of associations into association instances
105
105
  parsed_attributes = self.class.parse_associations(unset_attributes)
106
- attributes.update(parsed_attributes)
106
+
107
+ # Then merge the parsed_data into @attributes.
108
+ @attributes.merge!(parsed_attributes)
109
+ end
110
+ alias attributes= assign_attributes
111
+
112
+ def attributes
113
+ @attributes ||= HashWithIndifferentAccess.new
107
114
  end
108
115
 
109
116
  # Handles returning true for the accessible attributes
110
117
  #
111
118
  # @private
112
119
  def has_attribute?(attribute_name)
113
- attributes.include?(attribute_name)
120
+ @attributes.include?(attribute_name)
114
121
  end
115
122
 
116
123
  # Handles returning data for a specific attribute
117
124
  #
118
125
  # @private
119
126
  def get_attribute(attribute_name)
120
- attributes[attribute_name]
127
+ @attributes[attribute_name]
121
128
  end
129
+ alias attribute get_attribute
122
130
 
123
- # Override the method to prevent from returning the object ID
124
- #
125
- # @private
131
+ # Return the value of the model `primary_key` attribute
126
132
  def id
127
- attributes[self.class.primary_key]
133
+ @attributes[self.class.primary_key]
128
134
  end
129
135
 
130
136
  # Return `true` if the other object is also a Her::Model and has matching data
131
137
  #
132
138
  # @private
133
139
  def ==(other)
134
- other.is_a?(Her::Model) && attributes == other.attributes
140
+ other.is_a?(Her::Model) && @attributes == other.attributes
135
141
  end
136
142
 
137
143
  # Delegate to the == method
@@ -145,13 +151,14 @@ module Her
145
151
  # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
146
152
  # @private
147
153
  def hash
148
- attributes.hash
154
+ @attributes.hash
149
155
  end
150
156
 
151
157
  module ClassMethods
152
158
  # Initialize a collection of resources with raw data from an HTTP request
153
159
  #
154
160
  # @param [Array] parsed_data
161
+ # @private
155
162
  def new_collection(parsed_data)
156
163
  Her::Model::Attributes.initialize_collection(self, parsed_data)
157
164
  end
@@ -159,7 +166,6 @@ module Her
159
166
  # Define the attributes that will be used to track dirty attributes and validations
160
167
  #
161
168
  # @param [Array] attributes
162
- #
163
169
  # @example
164
170
  # class User
165
171
  # include Her::Model
@@ -172,17 +178,17 @@ module Her
172
178
  attribute = attribute.to_sym
173
179
 
174
180
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
175
- def #{attribute}
176
- @attributes.include?(:'#{attribute}') ? @attributes[:'#{attribute}'] : nil
181
+ unless instance_methods.include?(:'#{attribute}=')
182
+ def #{attribute}=(value)
183
+ self.send(:"#{attribute}_will_change!") if @attributes[:'#{attribute}'] != value
184
+ @attributes[:'#{attribute}'] = value
185
+ end
177
186
  end
178
187
 
179
- def #{attribute}=(value)
180
- self.send(:"#{attribute}_will_change!") if @attributes[:'#{attribute}'] != value
181
- @attributes[:'#{attribute}'] = value
182
- end
183
-
184
- def #{attribute}?
185
- @attributes.include?(:'#{attribute}') && @attributes[:'#{attribute}'].present?
188
+ unless instance_methods.include?(:'#{attribute}?')
189
+ def #{attribute}?
190
+ @attributes.include?(:'#{attribute}') && @attributes[:'#{attribute}'].present?
191
+ end
186
192
  end
187
193
  RUBY
188
194
  end
@@ -198,22 +204,7 @@ module Her
198
204
  # store_response_errors :server_errors
199
205
  # end
200
206
  def store_response_errors(value = nil)
201
- if @_her_store_response_errors
202
- remove_method @_her_store_response_errors
203
- remove_method :"#{@_her_store_response_errors}="
204
- end
205
-
206
- @_her_store_response_errors ||= begin
207
- superclass.store_response_errors if superclass.respond_to?(:store_response_errors)
208
- end
209
-
210
- return @_her_store_response_errors unless value
211
- @_her_store_response_errors = value
212
-
213
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
214
- def #{@_her_store_response_errors}; @response_errors; end
215
- def #{@_her_store_response_errors}=(value); @response_errors = value; end
216
- RUBY
207
+ store_her_data(:response_errors, value)
217
208
  end
218
209
 
219
210
  # Define the accessor in which the API response metadata (obtained from the parsing middleware) will be stored
@@ -226,22 +217,7 @@ module Her
226
217
  # store_metadata :server_data
227
218
  # end
228
219
  def store_metadata(value = nil)
229
- if @_her_store_metadata
230
- remove_method @_her_store_metadata
231
- remove_method :"#{@_her_store_metadata}="
232
- end
233
-
234
- @_her_store_metadata ||= begin
235
- superclass.store_metadata if superclass.respond_to?(:store_metadata)
236
- end
237
-
238
- return @_her_store_metadata unless value
239
- @_her_store_metadata = value
240
-
241
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
242
- def #{@_her_store_metadata}; @metadata; end
243
- def #{@_her_store_metadata}=(value); @metadata = value; end
244
- RUBY
220
+ store_her_data(:metadata, value)
245
221
  end
246
222
 
247
223
  # @private
@@ -251,6 +227,27 @@ module Her
251
227
  memo
252
228
  end
253
229
  end
230
+
231
+ private
232
+ # @private
233
+ def store_her_data(name, value)
234
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
235
+ if @_her_store_#{name} && value.present?
236
+ remove_method @_her_store_#{name}
237
+ remove_method @_her_store_#{name}.to_s + '='
238
+ end
239
+
240
+ @_her_store_#{name} ||= begin
241
+ superclass.store_#{name} if superclass.respond_to?(:store_#{name})
242
+ end
243
+
244
+ return @_her_store_#{name} unless value
245
+ @_her_store_#{name} = value
246
+
247
+ define_method(value) { @#{name} }
248
+ define_method(value.to_s+'=') { |value| @#{name} = value }
249
+ RUBY
250
+ end
254
251
  end
255
252
  end
256
253
  end
@@ -5,6 +5,22 @@ module Her
5
5
  extend ActiveSupport::Concern
6
6
  METHODS = [:get, :post, :put, :patch, :delete]
7
7
 
8
+ # For each HTTP method, define these class methods:
9
+ #
10
+ # - <method>(path, params)
11
+ # - <method>_raw(path, params, &block)
12
+ # - <method>_collection(path, params, &block)
13
+ # - <method>_resource(path, params, &block)
14
+ # - custom_<method>(*paths)
15
+ #
16
+ # @example
17
+ # class User
18
+ # include Her::Model
19
+ # custom_get :active
20
+ # end
21
+ #
22
+ # User.get(:popular) # GET "/users/popular"
23
+ # User.active # GET "/users/active"
8
24
  module ClassMethods
9
25
  # Change which API the model will use to make its HTTP requests
10
26
  #
@@ -32,8 +48,8 @@ module Her
32
48
  # Main request wrapper around Her::API. Used to make custom request to the API.
33
49
  #
34
50
  # @private
35
- def request(attrs={})
36
- request = her_api.request(attrs)
51
+ def request(params={})
52
+ request = her_api.request(params)
37
53
 
38
54
  if block_given?
39
55
  yield request[:parsed_data], request[:response]
@@ -42,18 +58,11 @@ module Her
42
58
  end
43
59
  end
44
60
 
45
- # For each HTTP method, define these methods:
46
- #
47
- # - <method>(path, attrs)
48
- # - <method>_raw(path, attrs, &block)
49
- # - <method>_collection(path, attrs, &block)
50
- # - <method>_resource(path, attrs, &block)
51
- # - custom_<method>(path, attrs)
52
61
  METHODS.each do |method|
53
62
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
54
- def #{method}(path, attrs={})
55
- path = build_request_path_from_string_or_symbol(path, attrs)
56
- send(:'#{method}_raw', path, attrs) do |parsed_data, response|
63
+ def #{method}(path, params={})
64
+ path = build_request_path_from_string_or_symbol(path, params)
65
+ send(:'#{method}_raw', path, params) do |parsed_data, response|
57
66
  if parsed_data[:data].is_a?(Array)
58
67
  new_collection(parsed_data)
59
68
  else
@@ -62,21 +71,21 @@ module Her
62
71
  end
63
72
  end
64
73
 
65
- def #{method}_raw(path, attrs={}, &block)
66
- path = build_request_path_from_string_or_symbol(path, attrs)
67
- request(attrs.merge(:_method => #{method.to_sym.inspect}, :_path => path), &block)
74
+ def #{method}_raw(path, params={}, &block)
75
+ path = build_request_path_from_string_or_symbol(path, params)
76
+ request(params.merge(:_method => #{method.to_sym.inspect}, :_path => path), &block)
68
77
  end
69
78
 
70
- def #{method}_collection(path, attrs={})
71
- path = build_request_path_from_string_or_symbol(path, attrs)
72
- send(:'#{method}_raw', build_request_path_from_string_or_symbol(path, attrs), attrs) do |parsed_data, response|
79
+ def #{method}_collection(path, params={})
80
+ path = build_request_path_from_string_or_symbol(path, params)
81
+ send(:'#{method}_raw', build_request_path_from_string_or_symbol(path, params), params) do |parsed_data, response|
73
82
  new_collection(parsed_data)
74
83
  end
75
84
  end
76
85
 
77
- def #{method}_resource(path, attrs={})
78
- path = build_request_path_from_string_or_symbol(path, attrs)
79
- send(:"#{method}_raw", path, attrs) do |parsed_data, response|
86
+ def #{method}_resource(path, params={})
87
+ path = build_request_path_from_string_or_symbol(path, params)
88
+ send(:"#{method}_raw", path, params) do |parsed_data, response|
80
89
  new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
81
90
  end
82
91
  end
@@ -86,9 +95,9 @@ module Her
86
95
  opts = paths.last.is_a?(Hash) ? paths.pop : Hash.new
87
96
 
88
97
  paths.each do |path|
89
- metaclass.send(:define_method, path) do |*attrs|
90
- attrs = attrs.first || Hash.new
91
- send(#{method.to_sym.inspect}, path, attrs)
98
+ metaclass.send(:define_method, path) do |*params|
99
+ params = params.first || Hash.new
100
+ send(#{method.to_sym.inspect}, path, params)
92
101
  end
93
102
  end
94
103
  end
@@ -32,6 +32,7 @@ module Her
32
32
  end
33
33
  end
34
34
 
35
+ # @private
35
36
  module ClassMethods
36
37
  # Finds a class at the same level as this one or at the global level.
37
38
  #
data/lib/her/model/orm.rb CHANGED
@@ -154,8 +154,8 @@ module Her
154
154
  # Delegate the following methods to `scoped`
155
155
  [:all, :where, :create, :build, :first_or_create, :first_or_initialize].each do |method|
156
156
  class_eval <<-RUBY, __FILE__, __LINE__ + 1
157
- def #{method}(*attrs)
158
- scoped.send(#{method.to_sym.inspect}, *attrs)
157
+ def #{method}(*params)
158
+ scoped.send(#{method.to_sym.inspect}, *params)
159
159
  end
160
160
  RUBY
161
161
  end
@@ -10,13 +10,14 @@ module Her
10
10
  # @user.to_params
11
11
  # # => { :id => 1, :name => 'John Smith' }
12
12
  def to_params
13
- self.class.include_root_in_json? ? { self.class.included_root_element => attributes.dup } : attributes.dup
13
+ self.class.include_root_in_json? ? { self.class.included_root_element => attributes.dup.symbolize_keys } : attributes.dup.symbolize_keys
14
14
  end
15
15
 
16
16
  module ClassMethods
17
17
  # Parse data before assigning it to a resource, based on `parse_root_in_json`.
18
18
  #
19
19
  # @param [Hash] data
20
+ # @private
20
21
  def parse(data)
21
22
  parse_root_in_json? ? data[parsed_root_element] : data
22
23
  end
@@ -87,16 +87,12 @@ module Her
87
87
 
88
88
  # Return a custom path based on the collection path and variable parameters
89
89
  #
90
- # @example
91
- # class User
92
- # include Her::Model
93
- # collection_path "/utilisateurs"
94
- # end
95
- #
96
- # User.all # Fetched via GET /utilisateurs
90
+ # @private
97
91
  def build_request_path(path=nil, parameters={})
92
+ parameters = parameters.try(:with_indifferent_access)
93
+
98
94
  unless path.is_a?(String)
99
- parameters = path || {}
95
+ parameters = path.try(:with_indifferent_access) || parameters
100
96
  path =
101
97
  if parameters.include?(primary_key) && parameters[primary_key]
102
98
  resource_path.dup
@@ -111,13 +107,13 @@ module Her
111
107
  path.gsub(/:([\w_]+)/) do
112
108
  # Look for :key or :_key, otherwise raise an exception
113
109
  value = $1.to_sym
114
- parameters.delete(value) || parameters.delete(:"_#{value}") || raise(Her::Errors::PathError.new("Missing :_#{$1} parameter to build the request path. Path is `#{path}`. Parameters are `#{parameters.inspect}`.", $1))
110
+ parameters.delete(value) || parameters.delete(:"_#{value}") || raise(Her::Errors::PathError.new("Missing :_#{$1} parameter to build the request path. Path is `#{path}`. Parameters are `#{parameters.symbolize_keys.inspect}`.", $1))
115
111
  end
116
112
  end
117
113
 
118
114
  # @private
119
- def build_request_path_from_string_or_symbol(path, attrs={})
120
- path.is_a?(Symbol) ? "#{build_request_path(attrs)}/#{path}" : path
115
+ def build_request_path_from_string_or_symbol(path, params={})
116
+ path.is_a?(Symbol) ? "#{build_request_path(params)}/#{path}" : path
121
117
  end
122
118
  end
123
119
  end