her 0.5.3 → 0.5.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.
Files changed (72) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -2
  4. data/.travis.yml +2 -2
  5. data/README.md +10 -16
  6. data/UPGRADE.md +4 -0
  7. data/examples/grape-and-her/.env.default +3 -0
  8. data/examples/grape-and-her/Procfile +2 -0
  9. data/examples/grape-and-her/README.md +27 -0
  10. data/examples/grape-and-her/api/Gemfile +11 -0
  11. data/examples/grape-and-her/api/Rakefile +14 -0
  12. data/examples/grape-and-her/api/app/api.rb +49 -0
  13. data/examples/grape-and-her/api/app/models/organization.rb +7 -0
  14. data/examples/grape-and-her/api/app/models/user.rb +9 -0
  15. data/examples/grape-and-her/api/app/views/organizations/_base.rabl +2 -0
  16. data/examples/grape-and-her/api/app/views/organizations/index.rabl +3 -0
  17. data/examples/grape-and-her/api/app/views/organizations/show.rabl +3 -0
  18. data/examples/grape-and-her/api/app/views/users/_base.rabl +8 -0
  19. data/examples/grape-and-her/api/app/views/users/index.rabl +3 -0
  20. data/examples/grape-and-her/api/app/views/users/show.rabl +3 -0
  21. data/examples/grape-and-her/api/config.ru +5 -0
  22. data/examples/grape-and-her/api/config/boot.rb +17 -0
  23. data/examples/grape-and-her/api/config/unicorn.rb +7 -0
  24. data/examples/grape-and-her/api/db/migrations/001_create_users.rb +11 -0
  25. data/examples/grape-and-her/api/db/migrations/002_create_organizations.rb +8 -0
  26. data/examples/grape-and-her/consumer/Gemfile +23 -0
  27. data/examples/grape-and-her/consumer/app/assets/stylesheets/application.scss +190 -0
  28. data/examples/grape-and-her/consumer/app/assets/stylesheets/reset.scss +53 -0
  29. data/examples/grape-and-her/consumer/app/consumer.rb +74 -0
  30. data/examples/grape-and-her/consumer/app/models/organization.rb +13 -0
  31. data/examples/grape-and-her/consumer/app/models/user.rb +13 -0
  32. data/examples/grape-and-her/consumer/app/views/index.haml +9 -0
  33. data/examples/grape-and-her/consumer/app/views/layout.haml +20 -0
  34. data/examples/grape-and-her/consumer/app/views/organizations/index.haml +25 -0
  35. data/examples/grape-and-her/consumer/app/views/organizations/show.haml +11 -0
  36. data/examples/grape-and-her/consumer/app/views/users/index.haml +33 -0
  37. data/examples/grape-and-her/consumer/app/views/users/show.haml +9 -0
  38. data/examples/grape-and-her/consumer/config.ru +20 -0
  39. data/examples/grape-and-her/consumer/config/boot.rb +30 -0
  40. data/examples/grape-and-her/consumer/config/unicorn.rb +7 -0
  41. data/examples/grape-and-her/consumer/lib/response_logger.rb +18 -0
  42. data/her.gemspec +2 -2
  43. data/lib/her/model.rb +22 -26
  44. data/lib/her/model/associations.rb +19 -19
  45. data/lib/her/model/attributes.rb +173 -0
  46. data/lib/her/model/base.rb +17 -0
  47. data/lib/her/model/http.rb +58 -242
  48. data/lib/her/model/introspection.rb +7 -8
  49. data/lib/her/model/nested_attributes.rb +3 -3
  50. data/lib/her/model/orm.rb +15 -205
  51. data/lib/her/model/parse.rb +86 -0
  52. data/lib/her/model/paths.rb +54 -14
  53. data/lib/her/version.rb +1 -1
  54. data/spec/model/attributes_spec.rb +139 -0
  55. data/spec/model/dirty_spec.rb +40 -0
  56. data/spec/model/introspection_spec.rb +5 -5
  57. data/spec/model/orm_spec.rb +14 -128
  58. data/spec/model/paths_spec.rb +26 -0
  59. data/spec/model/validations_spec.rb +17 -0
  60. data/spec/spec_helper.rb +7 -32
  61. data/spec/support/extensions/array.rb +5 -0
  62. data/spec/support/extensions/hash.rb +5 -0
  63. data/spec/support/macros/model_macros.rb +29 -0
  64. metadata +52 -15
  65. data/examples/twitter-oauth/Gemfile +0 -13
  66. data/examples/twitter-oauth/app.rb +0 -50
  67. data/examples/twitter-oauth/config.ru +0 -5
  68. data/examples/twitter-oauth/views/index.haml +0 -9
  69. data/examples/twitter-search/Gemfile +0 -12
  70. data/examples/twitter-search/app.rb +0 -55
  71. data/examples/twitter-search/config.ru +0 -5
  72. data/examples/twitter-search/views/index.haml +0 -9
@@ -30,24 +30,23 @@ module Her
30
30
  module ClassMethods
31
31
  # Finds a class at the same level as this one or at the global level.
32
32
  # @private
33
- def nearby_class(name)
34
- sibling_class(name) || name.constantize rescue nil
33
+ def her_nearby_class(name)
34
+ her_sibling_class(name) || name.constantize rescue nil
35
35
  end
36
36
 
37
37
  protected
38
38
  # Looks for a class at the same level as this one with the given name.
39
39
  # @private
40
- def sibling_class(name)
41
- if mod = self.containing_module
42
- @sibling_class ||= {}
43
- @sibling_class[mod] ||= {}
44
- @sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
40
+ def her_sibling_class(name)
41
+ if mod = self.her_containing_module
42
+ @_her_sibling_class ||= Hash.new { Hash.new }
43
+ @_her_sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
45
44
  end
46
45
  end
47
46
 
48
47
  # If available, returns the containing Module for this class.
49
48
  # @private
50
- def containing_module
49
+ def her_containing_module
51
50
  return unless self.name =~ /::/
52
51
  self.name.split("::")[0..-2].join("::").constantize
53
52
  end
@@ -37,8 +37,8 @@ module Her
37
37
 
38
38
  def assign_nested_attributes_for_has_many_association(association_name, attributes)
39
39
  association = self.class.associations[:has_many].find { |association| association[:name] == association_name }
40
- klass = self.class.nearby_class(association[:class_name])
41
- self.send("#{association[:name]}=", Her::Model::ORM.initialize_collection(klass, :data => attributes))
40
+ klass = self.class.her_nearby_class(association[:class_name])
41
+ self.send("#{association[:name]}=", Her::Model::Attributes.initialize_collection(klass, :data => attributes))
42
42
  end
43
43
 
44
44
  private
@@ -47,7 +47,7 @@ module Her
47
47
  if has_data?(association[:name])
48
48
  self.send("#{association[:name]}").assign_data(attributes)
49
49
  else
50
- klass = self.class.nearby_class(association[:class_name])
50
+ klass = self.class.her_nearby_class(association[:class_name])
51
51
  instance = klass.new(klass.parse(attributes))
52
52
  self.send("#{association[:name]}=", instance)
53
53
  end
data/lib/her/model/orm.rb CHANGED
@@ -3,125 +3,20 @@ module Her
3
3
  # This module adds ORM-like capabilities to the model
4
4
  module ORM
5
5
  extend ActiveSupport::Concern
6
- attr_accessor :attributes, :metadata, :response_errors
7
- alias :data :attributes
8
- alias :data= :attributes=
9
-
10
- # Initialize a new object with data received from an HTTP request
11
- def initialize(attributes={})
12
- attributes ||= {}
13
- @metadata = attributes.delete(:_metadata) || {}
14
- @response_errors = attributes.delete(:_errors) || {}
15
- @destroyed = attributes.delete(:_destroyed) || false
16
-
17
- update_attributes(attributes)
18
- end
19
-
20
- # Initialize a collection of resources
21
- # @private
22
- def self.initialize_collection(klass, parsed_data={})
23
- collection_data = parsed_data[:data].map do |item_data|
24
- resource = klass.new(klass.parse(item_data))
25
- resource.run_callbacks :find
26
- resource
27
- end
28
- Her::Collection.new(collection_data, parsed_data[:metadata], parsed_data[:errors])
29
- end
30
-
31
- # Use setter methods of model for each key / value pair in params
32
- # Return key / value pairs for which no setter method was defined on the model
33
- # @private
34
- def self.use_setter_methods(model, params)
35
- setter_method_names = model.class.setter_method_names
36
- params ||= {}
37
- params.inject({}) do |memo, (key, value)|
38
- setter_method = key.to_s + '='
39
- if setter_method_names.include?(setter_method)
40
- model.send(setter_method, value)
41
- else
42
- if key.is_a?(String)
43
- key = key.to_sym
44
- end
45
- memo[key] = value
46
- end
47
- memo
48
- end
49
- end
50
-
51
- # Handles missing methods
52
- # @private
53
- def method_missing(method, *args, &blk)
54
- if method.to_s =~ /[?=]$/ || attributes.include?(method)
55
- # Extract the attribute
56
- attribute = method.to_s.sub(/[?=]$/, '')
57
-
58
- # Create a new `attribute` methods set
59
- self.class.attributes(*attribute)
60
-
61
- # Resend the method!
62
- send(method, *args, &blk)
63
- else
64
- super
65
- end
66
- end
67
-
68
- # Handles returning true for the cases handled by method_missing
69
- def respond_to?(method, include_private = false)
70
- method.to_s.end_with?('=') || method.to_s.end_with?('?') || @attributes.include?(method) || super
71
- end
72
-
73
- def respond_to_missing?(method, include_private = false)
74
- method.to_s.end_with?('=') || method.to_s.end_with?('?') || @attributes.include?(method) || @attributes.include?(method) || super
75
- end
76
-
77
- # Assign new data to an instance
78
- def assign_attributes(new_attributes)
79
- new_attributes = Her::Model::ORM.use_setter_methods(self, new_attributes)
80
- attributes.update new_attributes
81
- end
82
- alias :assign_data :assign_attributes
83
-
84
- # Handles returning true for the accessible attributes
85
- def has_attribute?(attribute_name)
86
- attributes.include?(attribute_name)
87
- end
88
- alias :has_data? :has_attribute?
89
-
90
- def get_attribute(attribute_name)
91
- attributes[attribute_name]
92
- end
93
- alias :get_data :get_attribute
94
-
95
- # Override the method to prevent from returning the object ID (in ruby-1.8.7)
96
- # @private
97
- def id
98
- attributes[:id] || super
99
- end
100
6
 
101
7
  # Return `true` if a resource was not saved yet
102
8
  def new?
103
- !attributes.include?(:id)
9
+ attributes[self.class.primary_key].nil?
104
10
  end
105
11
 
106
- # Return `true` if the other object is also a Her::Model and has matching data
107
- def ==(other)
108
- other.is_a?(Her::Model) && attributes == other.attributes
109
- end
110
-
111
- # Delegate to the == method
112
- def eql?(other)
113
- self == other
114
- end
115
-
116
- # Delegate to @attributes, allowing models to act correctly in code like:
117
- # [ Model.find(1), Model.find(1) ].uniq # => [ Model.find(1) ]
118
- def hash
119
- attributes.hash
12
+ # Return `true` if a resource is not `#new?`
13
+ def persisted?
14
+ !new?
120
15
  end
121
16
 
122
17
  # Return whether the object has been destroyed
123
18
  def destroyed?
124
- @destroyed
19
+ @destroyed == true
125
20
  end
126
21
 
127
22
  # Save a resource
@@ -141,18 +36,18 @@ module Her
141
36
  params = to_params
142
37
  resource = self
143
38
 
144
- if attributes[:id]
145
- callback = :update
146
- method = :put
147
- else
39
+ if new?
148
40
  callback = :create
149
41
  method = :post
42
+ else
43
+ callback = :update
44
+ method = :put
150
45
  end
151
46
 
152
47
  run_callbacks callback do
153
48
  run_callbacks :save do
154
49
  self.class.request(params.merge(:_method => method, :_path => "#{request_path}")) do |parsed_data, response|
155
- update_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
50
+ assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
156
51
  self.metadata = parsed_data[:metadata]
157
52
  self.response_errors = parsed_data[:errors]
158
53
  self.changed_attributes.clear if self.changed_attributes.present?
@@ -175,7 +70,7 @@ module Her
175
70
  resource = self
176
71
  run_callbacks :destroy do
177
72
  self.class.request(:_method => :delete, :_path => "#{request_path}") do |parsed_data, response|
178
- update_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
73
+ assign_attributes(self.class.parse(parsed_data[:data])) if parsed_data[:data].any?
179
74
  self.metadata = parsed_data[:metadata]
180
75
  self.response_errors = parsed_data[:errors]
181
76
  @destroyed = true
@@ -184,72 +79,7 @@ module Her
184
79
  self
185
80
  end
186
81
 
187
- # @private
188
- def update_attributes(raw_data)
189
- @attributes ||= {}
190
- # Use setter methods first, then translate attributes of associations
191
- # into association instances, then merge the parsed_data into @attributes.
192
- unset_attributes = Her::Model::ORM.use_setter_methods(self, raw_data)
193
- parsed_attributes = self.class.parse_associations(unset_attributes)
194
- attributes.update(parsed_attributes)
195
- end
196
-
197
- # Convert into a hash of request parameters
198
- #
199
- # @example
200
- # @user.to_params
201
- # # => { :id => 1, :name => 'John Smith' }
202
- def to_params
203
- if self.class.include_root_in_json
204
- { (self.class.include_root_in_json == true ? self.class.root_element : self.class.include_root_in_json) => attributes.dup }
205
- else
206
- attributes.dup
207
- end
208
- end
209
-
210
82
  module ClassMethods
211
- # Initialize a collection of resources with raw data from an HTTP request
212
- #
213
- # @param [Array] parsed_data
214
- def new_collection(parsed_data)
215
- Her::Model::ORM.initialize_collection(self, parsed_data)
216
- end
217
-
218
- # Define the attributes that will be used to track dirty attributes and validations
219
- #
220
- # @param [Array] attributes
221
- def attributes(*attributes)
222
- define_attribute_methods attributes
223
-
224
- attributes.each do |attribute|
225
- attribute = attribute.to_sym
226
-
227
- define_method "#{attribute}".to_sym do
228
- @attributes.include?(attribute) ? @attributes[attribute] : nil
229
- end
230
-
231
- define_method "#{attribute}=".to_sym do |value|
232
- self.send("#{attribute}_will_change!".to_sym) if @attributes[attribute] != value
233
- @attributes[attribute] = value
234
- end
235
-
236
- define_method "#{attribute}?".to_sym do
237
- @attributes.include?(attribute) && @attributes[attribute].present?
238
- end
239
- end
240
- end
241
-
242
- # Parse data before assigning it to a resource
243
- #
244
- # @param [Hash] data
245
- def parse(data)
246
- if parse_root_in_json
247
- parse_root_in_json == true ? data[root_element.to_sym] : data[parse_root_in_json]
248
- else
249
- data
250
- end
251
- end
252
-
253
83
  # Fetch specific resource(s) by their ID
254
84
  #
255
85
  # @example
@@ -263,7 +93,7 @@ module Her
263
93
  params = ids.last.is_a?(Hash) ? ids.pop : {}
264
94
  results = ids.flatten.compact.uniq.map do |id|
265
95
  resource = nil
266
- request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
96
+ request(params.merge(:_method => :get, :_path => "#{build_request_path(params.merge(primary_key => id))}")) do |parsed_data, response|
267
97
  if response.success?
268
98
  resource = new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
269
99
  resource.run_callbacks :find
@@ -304,7 +134,7 @@ module Her
304
134
  request(params.merge(:_method => :post, :_path => "#{build_request_path(params)}")) do |parsed_data, response|
305
135
  data = parse(parsed_data[:data])
306
136
  resource.instance_eval do
307
- update_attributes(data)
137
+ assign_attributes(data)
308
138
  @metadata = parsed_data[:metadata]
309
139
  @response_errors = parsed_data[:errors]
310
140
  @changed_attributes.clear if @changed_attributes.present?
@@ -321,7 +151,7 @@ module Her
321
151
  # @user = User.save_existing(1, { :fullname => "Tobias Fünke" })
322
152
  # # Called via PUT "/users/1"
323
153
  def save_existing(id, params)
324
- resource = new(params.merge(:id => id))
154
+ resource = new(params.merge(primary_key => id))
325
155
  resource.save
326
156
  resource
327
157
  end
@@ -332,30 +162,10 @@ module Her
332
162
  # User.destroy_existing(1)
333
163
  # # Called via DELETE "/users/1"
334
164
  def destroy_existing(id, params={})
335
- request(params.merge(:_method => :delete, :_path => "#{build_request_path(params.merge(:id => id))}")) do |parsed_data, response|
165
+ request(params.merge(:_method => :delete, :_path => "#{build_request_path(params.merge(primary_key => id))}")) do |parsed_data, response|
336
166
  new(parse(parsed_data[:data]).merge(:_destroyed => true))
337
167
  end
338
168
  end
339
-
340
- # @private
341
- def setter_method_names
342
- @setter_method_names ||= instance_methods.inject(Set.new) do |memo, method_name|
343
- memo << method_name.to_s if method_name.to_s.end_with?('=')
344
- memo
345
- end
346
- end
347
-
348
- # Return or change the value of `include_root_in_json`
349
- def include_root_in_json(value=nil)
350
- return @include_root_in_json if value.nil?
351
- @include_root_in_json = value
352
- end
353
-
354
- # Return or change the value of `parse_root_in`
355
- def parse_root_in_json(value=nil)
356
- return @parse_root_in_json if value.nil?
357
- @parse_root_in_json = value
358
- end
359
169
  end
360
170
  end
361
171
  end
@@ -0,0 +1,86 @@
1
+ module Her
2
+ module Model
3
+ # This module handles resource data parsing at the model level (after the parsing middleware)
4
+ module Parse
5
+ extend ActiveSupport::Concern
6
+
7
+ # Convert into a hash of request parameters, based on `include_root_in_json`.
8
+ #
9
+ # @example
10
+ # @user.to_params
11
+ # # => { :id => 1, :name => 'John Smith' }
12
+ def to_params
13
+ self.class.include_root_in_json? ? { self.class.included_root_element => attributes.dup } : attributes.dup
14
+ end
15
+
16
+ module ClassMethods
17
+ # Parse data before assigning it to a resource, based on `parse_root_in_json`.
18
+ #
19
+ # @param [Hash] data
20
+ def parse(data)
21
+ parse_root_in_json? ? data[parsed_root_element] : data
22
+ end
23
+
24
+ # Return or change the value of `include_root_in_json`
25
+ #
26
+ # @example
27
+ # class User
28
+ # include Her::Model
29
+ # include_root_in_json true
30
+ # end
31
+ def include_root_in_json(value=nil)
32
+ return @include_root_in_json if value.nil?
33
+ @include_root_in_json = value
34
+ end
35
+
36
+ # Return or change the value of `parse_root_in`
37
+ #
38
+ # @example
39
+ # class User
40
+ # include Her::Model
41
+ # parse_root_in_json true
42
+ # end
43
+ def parse_root_in_json(value=nil)
44
+ return @parse_root_in_json if value.nil?
45
+ @parse_root_in_json = value
46
+ end
47
+
48
+ # Return or change the value of `root_element`
49
+ #
50
+ # @example
51
+ # class User
52
+ # include Her::Model
53
+ # parse_root_in_json true
54
+ # root_element :huh
55
+ # end
56
+ #
57
+ # user = User.find(1) # { :huh => { :id => 1, :name => "Tobias" } }
58
+ # user.name # => "Tobias"
59
+ def root_element(value=nil)
60
+ return @root_element if value.nil?
61
+ @root_element = value
62
+ end
63
+
64
+ # @private
65
+ def parse_root_in_json?
66
+ @parse_root_in_json
67
+ end
68
+
69
+ # @private
70
+ def include_root_in_json?
71
+ @include_root_in_json
72
+ end
73
+
74
+ # @private
75
+ def included_root_element
76
+ include_root_in_json == true ? root_element.to_sym : include_root_in_json
77
+ end
78
+
79
+ # @private
80
+ def parsed_root_element
81
+ parse_root_in_json == true ? root_element.to_sym : parse_root_in_json
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -11,12 +11,29 @@ module Her
11
11
  # end
12
12
  #
13
13
  # User.find(1) # Fetched via GET /utilisateurs/1
14
- def request_path
15
- self.class.build_request_path(attributes.dup)
14
+ #
15
+ # @param [Hash] params An optional set of additional parameters for
16
+ # path construction. These will not override attributes of the resource.
17
+ def request_path(params = {})
18
+ self.class.build_request_path(params.merge(attributes.dup))
16
19
  end
17
20
 
18
21
  module ClassMethods
19
22
 
23
+ # Define the primary key field that will be used to find and save records
24
+ #
25
+ # @example
26
+ # class User
27
+ # include Her::Model
28
+ # primary_key 'UserId'
29
+ # end
30
+ #
31
+ # @param [Symbol] field
32
+ def primary_key(field = nil)
33
+ return @her_primary_key if field.nil?
34
+ @her_primary_key = field.to_sym
35
+ end
36
+
20
37
  # Defines a custom collection path for the resource
21
38
  #
22
39
  # @example
@@ -25,13 +42,13 @@ module Her
25
42
  # collection_path "/users"
26
43
  # end
27
44
  def collection_path(path=nil)
28
- @her_collection_path ||= begin
45
+ @_her_collection_path ||= begin
29
46
  superclass.collection_path.dup if superclass.respond_to?(:collection_path)
30
47
  end
31
48
 
32
- return @her_collection_path unless path
33
- @her_resource_path = "#{path}/:id"
34
- @her_collection_path = path
49
+ return @_her_collection_path unless path
50
+ @_her_resource_path = "#{path}/:id"
51
+ @_her_collection_path = path
35
52
  end
36
53
 
37
54
  # Defines a custom resource path for the resource
@@ -41,13 +58,29 @@ module Her
41
58
  # include Her::Model
42
59
  # resource_path "/users/:id"
43
60
  # end
61
+ #
62
+ # Note that, if used in combination with resource_path, you may specify
63
+ # either the real primary key or the string ':id'. For example:
64
+ #
65
+ # @example
66
+ # class User
67
+ # include Her::Model
68
+ # primary_key 'user_id'
69
+ #
70
+ # # This works because we'll have a user_id attribute
71
+ # resource_path '/users/:user_id'
72
+ #
73
+ # # This works because we replace :id with :user_id
74
+ # resource_path '/users/:id'
75
+ # end
76
+ #
44
77
  def resource_path(path=nil)
45
- @her_resource_path ||= begin
78
+ @_her_resource_path ||= begin
46
79
  superclass.resource_path.dup if superclass.respond_to?(:resource_path)
47
80
  end
48
81
 
49
- return @her_resource_path unless path
50
- @her_resource_path = path
82
+ return @_her_resource_path unless path
83
+ @_her_resource_path = path
51
84
  end
52
85
 
53
86
  # Return a custom path based on the collection path and variable parameters
@@ -62,7 +95,15 @@ module Her
62
95
  def build_request_path(path=nil, parameters={})
63
96
  unless path.is_a?(String)
64
97
  parameters = path || {}
65
- path = parameters.include?(:id) && !parameters[:id].nil? ? resource_path : collection_path
98
+ path =
99
+ if parameters.include?(primary_key) && parameters[primary_key]
100
+ resource_path.dup
101
+ else
102
+ collection_path.dup
103
+ end
104
+
105
+ # Replace :id with our actual primary key
106
+ path.gsub!(/(\A|\/):id(\Z|\/)/, "\\1:#{primary_key}\\2")
66
107
  end
67
108
 
68
109
  path.gsub(/:([\w_]+)/) do
@@ -71,10 +112,9 @@ module Her
71
112
  end
72
113
  end
73
114
 
74
- # Return or change the value of `root_element`
75
- def root_element(value=nil)
76
- return @root_element if value.nil?
77
- @root_element = value
115
+ # @private
116
+ def build_request_path_from_string_or_symbol(path, attrs={})
117
+ path.is_a?(Symbol) ? "#{build_request_path(attrs)}/#{path}" : path
78
118
  end
79
119
  end
80
120
  end