her 0.6.3 → 0.6.4

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