her 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
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