her 1.0.1 → 1.0.2
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -1279
- data/.rubocop_todo.yml +232 -0
- data/README.md +16 -0
- data/her.gemspec +3 -2
- data/lib/her/api.rb +8 -7
- data/lib/her/collection.rb +2 -1
- data/lib/her/errors.rb +3 -1
- data/lib/her/json_api/model.rb +8 -12
- data/lib/her/middleware.rb +1 -1
- data/lib/her/middleware/accept_json.rb +1 -0
- data/lib/her/middleware/first_level_parse_json.rb +6 -5
- data/lib/her/middleware/json_api_parser.rb +6 -5
- data/lib/her/middleware/parse_json.rb +2 -1
- data/lib/her/middleware/second_level_parse_json.rb +6 -5
- data/lib/her/model/associations.rb +6 -6
- data/lib/her/model/associations/association.rb +7 -9
- data/lib/her/model/associations/association_proxy.rb +2 -3
- data/lib/her/model/associations/belongs_to_association.rb +1 -1
- data/lib/her/model/attributes.rb +6 -6
- data/lib/her/model/base.rb +2 -2
- data/lib/her/model/http.rb +1 -1
- data/lib/her/model/introspection.rb +5 -3
- data/lib/her/model/nested_attributes.rb +1 -1
- data/lib/her/model/orm.rb +9 -8
- data/lib/her/model/parse.rb +9 -12
- data/lib/her/model/paths.rb +3 -4
- data/lib/her/model/relation.rb +5 -4
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +3 -0
- data/spec/middleware/accept_json_spec.rb +1 -0
- data/spec/middleware/first_level_parse_json_spec.rb +2 -1
- data/spec/middleware/json_api_parser_spec.rb +1 -0
- data/spec/middleware/second_level_parse_json_spec.rb +1 -0
- data/spec/model/associations/association_proxy_spec.rb +1 -0
- data/spec/model/associations_spec.rb +4 -3
- data/spec/model/attributes_spec.rb +5 -3
- data/spec/model/callbacks_spec.rb +14 -15
- data/spec/model/dirty_spec.rb +1 -0
- data/spec/model/http_spec.rb +1 -0
- data/spec/model/introspection_spec.rb +3 -2
- data/spec/model/nested_attributes_spec.rb +1 -0
- data/spec/model/orm_spec.rb +22 -16
- data/spec/model/parse_spec.rb +3 -0
- data/spec/model/paths_spec.rb +1 -0
- data/spec/model/relation_spec.rb +3 -2
- data/spec/model/validations_spec.rb +1 -0
- data/spec/model_spec.rb +1 -0
- data/spec/support/extensions/array.rb +1 -0
- data/spec/support/extensions/hash.rb +1 -0
- metadata +12 -11
@@ -3,6 +3,7 @@ module Her
|
|
3
3
|
# This middleware expects the resource/collection data to be contained in the `data`
|
4
4
|
# key of the JSON object
|
5
5
|
class JsonApiParser < ParseJSON
|
6
|
+
|
6
7
|
# Parse the response body
|
7
8
|
#
|
8
9
|
# @param [String] body The response body
|
@@ -25,11 +26,11 @@ module Her
|
|
25
26
|
# @private
|
26
27
|
def on_complete(env)
|
27
28
|
env[:body] = case env[:status]
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
when 204, 304
|
30
|
+
parse('{}')
|
31
|
+
else
|
32
|
+
parse(env[:body])
|
33
|
+
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Her
|
2
2
|
module Middleware
|
3
3
|
class ParseJSON < Faraday::Response::Middleware
|
4
|
+
|
4
5
|
# @private
|
5
6
|
def parse_json(body = nil)
|
6
7
|
body = '{}' if body.blank?
|
@@ -12,7 +13,7 @@ module Her
|
|
12
13
|
raise Her::Errors::ParseError, message
|
13
14
|
end
|
14
15
|
|
15
|
-
raise Her::Errors::ParseError, message unless json.is_a?(Hash)
|
16
|
+
raise Her::Errors::ParseError, message unless json.is_a?(Hash) || json.is_a?(Array)
|
16
17
|
|
17
18
|
json
|
18
19
|
end
|
@@ -3,6 +3,7 @@ module Her
|
|
3
3
|
# This middleware expects the resource/collection data to be contained in the `data`
|
4
4
|
# key of the JSON object
|
5
5
|
class SecondLevelParseJSON < ParseJSON
|
6
|
+
|
6
7
|
# Parse the response body
|
7
8
|
#
|
8
9
|
# @param [String] body The response body
|
@@ -25,11 +26,11 @@ module Her
|
|
25
26
|
# @private
|
26
27
|
def on_complete(env)
|
27
28
|
env[:body] = case env[:status]
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
when 204
|
30
|
+
parse('{}')
|
31
|
+
else
|
32
|
+
parse(env[:body])
|
33
|
+
end
|
33
34
|
end
|
34
35
|
end
|
35
36
|
end
|
@@ -32,18 +32,18 @@ module Her
|
|
32
32
|
# @private
|
33
33
|
def associations
|
34
34
|
@_her_associations ||= begin
|
35
|
-
superclass.respond_to?(:associations) ? superclass.associations.dup : Hash.new { |h,k| h[k] = [] }
|
35
|
+
superclass.respond_to?(:associations) ? superclass.associations.dup : Hash.new { |h, k| h[k] = [] }
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
39
|
# @private
|
40
40
|
def association_names
|
41
|
-
associations.inject([]) { |memo, (
|
41
|
+
associations.inject([]) { |memo, (_, details)| memo << details }.flatten.map { |a| a[:name] }
|
42
42
|
end
|
43
43
|
|
44
44
|
# @private
|
45
45
|
def association_keys
|
46
|
-
associations.inject([]) { |memo, (
|
46
|
+
associations.inject([]) { |memo, (_, details)| memo << details }.flatten.map { |a| a[:data_key] }
|
47
47
|
end
|
48
48
|
|
49
49
|
# Parse associations data after initializing a new object
|
@@ -81,7 +81,7 @@ module Her
|
|
81
81
|
# @user = User.find(1)
|
82
82
|
# @user.articles # => [#<Article(articles/2) id=2 title="Hello world.">]
|
83
83
|
# # Fetched via GET "/users/1/articles"
|
84
|
-
def has_many(name, opts={})
|
84
|
+
def has_many(name, opts = {})
|
85
85
|
Her::Model::Associations::HasManyAssociation.attach(self, name, opts)
|
86
86
|
end
|
87
87
|
|
@@ -106,7 +106,7 @@ module Her
|
|
106
106
|
# @user = User.find(1)
|
107
107
|
# @user.organization # => #<Organization(organizations/2) id=2 name="Foobar Inc.">
|
108
108
|
# # Fetched via GET "/users/1/organization"
|
109
|
-
def has_one(name, opts={})
|
109
|
+
def has_one(name, opts = {})
|
110
110
|
Her::Model::Associations::HasOneAssociation.attach(self, name, opts)
|
111
111
|
end
|
112
112
|
|
@@ -132,7 +132,7 @@ module Her
|
|
132
132
|
# @user = User.find(1) # => #<User(users/1) id=1 team_id=2 name="Tobias">
|
133
133
|
# @user.team # => #<Team(teams/2) id=2 name="Developers">
|
134
134
|
# # Fetched via GET "/teams/2"
|
135
|
-
def belongs_to(name, opts={})
|
135
|
+
def belongs_to(name, opts = {})
|
136
136
|
Her::Model::Associations::BelongsToAssociation.attach(self, name, opts)
|
137
137
|
end
|
138
138
|
end
|
@@ -2,6 +2,7 @@ module Her
|
|
2
2
|
module Model
|
3
3
|
module Associations
|
4
4
|
class Association
|
5
|
+
|
5
6
|
# @private
|
6
7
|
attr_accessor :params
|
7
8
|
|
@@ -47,7 +48,7 @@ module Her
|
|
47
48
|
return @parent.attributes[@name] unless @params.any? || @parent.attributes[@name].blank?
|
48
49
|
return @opts[:default].try(:dup) if @parent.new?
|
49
50
|
|
50
|
-
path = build_association_path
|
51
|
+
path = build_association_path -> { "#{@parent.request_path(@params)}#{@opts[:path]}" }
|
51
52
|
@klass.get(path, @params).tap do |result|
|
52
53
|
@cached_result = result unless @params.any?
|
53
54
|
end
|
@@ -55,11 +56,9 @@ module Her
|
|
55
56
|
|
56
57
|
# @private
|
57
58
|
def build_association_path(code)
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
return nil
|
62
|
-
end
|
59
|
+
instance_exec(&code)
|
60
|
+
rescue Her::Errors::PathError
|
61
|
+
nil
|
63
62
|
end
|
64
63
|
|
65
64
|
# @private
|
@@ -81,7 +80,7 @@ module Her
|
|
81
80
|
# user.comments.where(:approved => 1) # Fetched via GET "/users/1/comments?approved=1
|
82
81
|
def where(params = {})
|
83
82
|
return self if params.blank? && @parent.attributes[@name].blank?
|
84
|
-
AssociationProxy.new
|
83
|
+
AssociationProxy.new clone.tap { |a| a.params = a.params.merge(params) }
|
85
84
|
end
|
86
85
|
alias all where
|
87
86
|
|
@@ -97,7 +96,7 @@ module Her
|
|
97
96
|
# user.comments.find(3) # Fetched via GET "/users/1/comments/3
|
98
97
|
def find(id)
|
99
98
|
return nil if id.blank?
|
100
|
-
path = build_association_path
|
99
|
+
path = build_association_path -> { "#{@parent.request_path(@params)}#{@opts[:path]}/#{id}" }
|
101
100
|
@klass.get_resource(path, @params)
|
102
101
|
end
|
103
102
|
|
@@ -123,7 +122,6 @@ module Her
|
|
123
122
|
reset
|
124
123
|
fetch
|
125
124
|
end
|
126
|
-
|
127
125
|
end
|
128
126
|
end
|
129
127
|
end
|
@@ -15,7 +15,7 @@ module Her
|
|
15
15
|
end
|
16
16
|
|
17
17
|
install_proxy_methods :association,
|
18
|
-
|
18
|
+
:build, :create, :where, :find, :all, :assign_nested_attributes, :reload
|
19
19
|
|
20
20
|
# @private
|
21
21
|
def initialize(association)
|
@@ -28,7 +28,7 @@ module Her
|
|
28
28
|
|
29
29
|
# @private
|
30
30
|
def method_missing(name, *args, &block)
|
31
|
-
if
|
31
|
+
if name == :object_id # avoid redefining object_id
|
32
32
|
return association.fetch.object_id
|
33
33
|
end
|
34
34
|
|
@@ -38,7 +38,6 @@ module Her
|
|
38
38
|
# resend message to fetched object
|
39
39
|
__send__(name, *args, &block)
|
40
40
|
end
|
41
|
-
|
42
41
|
end
|
43
42
|
end
|
44
43
|
end
|
@@ -80,7 +80,7 @@ module Her
|
|
80
80
|
return @parent.attributes[@name] unless @params.any? || @parent.attributes[@name].blank?
|
81
81
|
|
82
82
|
path_params = @parent.attributes.merge(@params.merge(@klass.primary_key => foreign_key_value))
|
83
|
-
path = build_association_path
|
83
|
+
path = build_association_path -> { @klass.build_request_path(path_params) }
|
84
84
|
@klass.get_resource(path, @params).tap do |result|
|
85
85
|
@cached_result = result if @params.blank?
|
86
86
|
end
|
data/lib/her/model/attributes.rb
CHANGED
@@ -23,7 +23,7 @@ module Her
|
|
23
23
|
# u.name = "Tobias"
|
24
24
|
# end
|
25
25
|
# # => #<User name="Tobias">
|
26
|
-
def initialize(attributes={})
|
26
|
+
def initialize(attributes = {})
|
27
27
|
attributes ||= {}
|
28
28
|
@metadata = attributes.delete(:_metadata) || {}
|
29
29
|
@response_errors = attributes.delete(:_errors) || {}
|
@@ -151,19 +151,18 @@ module Her
|
|
151
151
|
end
|
152
152
|
|
153
153
|
module ClassMethods
|
154
|
-
|
155
154
|
# Initialize a single resource
|
156
155
|
#
|
157
156
|
# @private
|
158
157
|
def instantiate_record(klass, parsed_data)
|
159
|
-
if record = parsed_data[:data]
|
158
|
+
if (record = parsed_data[:data]) && record.is_a?(klass)
|
160
159
|
record
|
161
160
|
else
|
162
161
|
attributes = klass.parse(record).merge(_metadata: parsed_data[:metadata],
|
163
162
|
_errors: parsed_data[:errors])
|
164
|
-
klass.new(attributes).tap do |
|
165
|
-
|
166
|
-
|
163
|
+
klass.new(attributes).tap do |record_instance|
|
164
|
+
record_instance.send :clear_changes_information
|
165
|
+
record_instance.run_callbacks :find
|
167
166
|
end
|
168
167
|
end
|
169
168
|
end
|
@@ -289,6 +288,7 @@ module Her
|
|
289
288
|
end
|
290
289
|
|
291
290
|
private
|
291
|
+
|
292
292
|
# @private
|
293
293
|
def store_her_data(name, value)
|
294
294
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
data/lib/her/model/base.rb
CHANGED
@@ -11,7 +11,7 @@ module Her
|
|
11
11
|
# @private
|
12
12
|
def has_key?(attribute_name)
|
13
13
|
has_attribute?(attribute_name) ||
|
14
|
-
|
14
|
+
has_association?(attribute_name)
|
15
15
|
end
|
16
16
|
|
17
17
|
# Returns
|
@@ -21,7 +21,7 @@ module Her
|
|
21
21
|
# @private
|
22
22
|
def [](attribute_name)
|
23
23
|
get_attribute(attribute_name) ||
|
24
|
-
|
24
|
+
get_association(attribute_name)
|
25
25
|
end
|
26
26
|
|
27
27
|
# @private
|
data/lib/her/model/http.rb
CHANGED
@@ -22,6 +22,7 @@ module Her
|
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
25
|
+
|
25
26
|
def attribute_for_inspect(value)
|
26
27
|
if value.is_a?(String) && value.length > 50
|
27
28
|
"#{value[0..50]}...".inspect
|
@@ -42,11 +43,12 @@ module Her
|
|
42
43
|
end
|
43
44
|
|
44
45
|
protected
|
46
|
+
|
45
47
|
# Looks for a class at the same level as this one with the given name.
|
46
48
|
#
|
47
49
|
# @private
|
48
50
|
def her_sibling_class(name)
|
49
|
-
if mod =
|
51
|
+
if mod = her_containing_module
|
50
52
|
@_her_sibling_class ||= Hash.new { Hash.new }
|
51
53
|
@_her_sibling_class[mod][name] ||= "#{mod.name}::#{name}".constantize rescue nil
|
52
54
|
end
|
@@ -56,8 +58,8 @@ module Her
|
|
56
58
|
#
|
57
59
|
# @private
|
58
60
|
def her_containing_module
|
59
|
-
return unless
|
60
|
-
|
61
|
+
return unless name =~ /::/
|
62
|
+
name.split("::")[0..-2].join("::").constantize
|
61
63
|
end
|
62
64
|
end
|
63
65
|
end
|
@@ -25,7 +25,7 @@ module Her
|
|
25
25
|
|
26
26
|
associations.each do |association_name|
|
27
27
|
unless allowed_association_names.include?(association_name)
|
28
|
-
raise Her::Errors::AssociationUnknownError
|
28
|
+
raise Her::Errors::AssociationUnknownError, "Unknown association name :#{association_name}"
|
29
29
|
end
|
30
30
|
|
31
31
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
data/lib/her/model/orm.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
module Her
|
3
4
|
module Model
|
4
5
|
# This module adds ORM-like capabilities to the model
|
@@ -54,7 +55,7 @@ module Her
|
|
54
55
|
|
55
56
|
# Similar to save(), except that ResourceInvalid is raised if the save fails
|
56
57
|
def save!
|
57
|
-
|
58
|
+
unless save
|
58
59
|
raise Her::Errors::ResourceInvalid, self
|
59
60
|
end
|
60
61
|
self
|
@@ -195,7 +196,7 @@ module Her
|
|
195
196
|
#
|
196
197
|
# User.all # Called via GET "/users?admin=1"
|
197
198
|
# User.new.admin # => 1
|
198
|
-
def default_scope(block=nil)
|
199
|
+
def default_scope(block = nil)
|
199
200
|
@_her_default_scope ||= (!respond_to?(:default_scope) && superclass.respond_to?(:default_scope)) ? superclass.default_scope : scoped
|
200
201
|
@_her_default_scope = @_her_default_scope.instance_exec(&block) unless block.nil?
|
201
202
|
@_her_default_scope
|
@@ -227,7 +228,7 @@ module Her
|
|
227
228
|
# @example
|
228
229
|
# User.destroy_existing(1)
|
229
230
|
# # Called via DELETE "/users/1"
|
230
|
-
def destroy_existing(id, params={})
|
231
|
+
def destroy_existing(id, params = {})
|
231
232
|
request(params.merge(:_method => method_for(:destroy), :_path => build_request_path(params.merge(primary_key => id)))) do |parsed_data, response|
|
232
233
|
data = parse(parsed_data[:data])
|
233
234
|
metadata = parsed_data[:metadata]
|
@@ -256,15 +257,15 @@ module Her
|
|
256
257
|
# If the request_new_object_on_build flag is set, the new object is requested via API.
|
257
258
|
def build(attributes = {})
|
258
259
|
params = attributes
|
259
|
-
return
|
260
|
+
return new(params) unless request_new_object_on_build?
|
260
261
|
|
261
|
-
path =
|
262
|
-
method =
|
262
|
+
path = build_request_path(params.merge(primary_key => 'new'))
|
263
|
+
method = method_for(:new)
|
263
264
|
|
264
265
|
resource = nil
|
265
|
-
|
266
|
+
request(params.merge(:_method => method, :_path => path)) do |parsed_data, response|
|
266
267
|
if response.success?
|
267
|
-
resource =
|
268
|
+
resource = new_from_parsed_data(parsed_data)
|
268
269
|
end
|
269
270
|
end
|
270
271
|
resource
|
data/lib/her/model/parse.rb
CHANGED
@@ -10,7 +10,7 @@ module Her
|
|
10
10
|
# @user.to_params
|
11
11
|
# # => { :id => 1, :name => 'John Smith' }
|
12
12
|
def to_params
|
13
|
-
self.class.to_params(
|
13
|
+
self.class.to_params(attributes, changes)
|
14
14
|
end
|
15
15
|
|
16
16
|
module ClassMethods
|
@@ -31,7 +31,7 @@ module Her
|
|
31
31
|
end
|
32
32
|
|
33
33
|
# @private
|
34
|
-
def to_params(attributes, changes={})
|
34
|
+
def to_params(attributes, changes = {})
|
35
35
|
filtered_attributes = attributes.each_with_object({}) do |(key, value), memo|
|
36
36
|
case value
|
37
37
|
when Her::Model
|
@@ -45,10 +45,7 @@ module Her
|
|
45
45
|
filtered_attributes.merge!(embeded_params(attributes))
|
46
46
|
|
47
47
|
if her_api.options[:send_only_modified_attributes]
|
48
|
-
filtered_attributes
|
49
|
-
hash[attribute] = filtered_attributes[attribute]
|
50
|
-
hash
|
51
|
-
end
|
48
|
+
filtered_attributes.slice! *changes.keys.map(&:to_sym)
|
52
49
|
end
|
53
50
|
|
54
51
|
if include_root_in_json?
|
@@ -133,11 +130,11 @@ module Her
|
|
133
130
|
# user.name # => "Tobias"
|
134
131
|
def root_element(value = nil)
|
135
132
|
if value.nil?
|
136
|
-
if json_api_format?
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
133
|
+
@_her_root_element ||= if json_api_format?
|
134
|
+
name.split("::").last.pluralize.underscore.to_sym
|
135
|
+
else
|
136
|
+
name.split("::").last.underscore.to_sym
|
137
|
+
end
|
141
138
|
else
|
142
139
|
@_her_root_element = value.to_sym
|
143
140
|
end
|
@@ -145,7 +142,7 @@ module Her
|
|
145
142
|
|
146
143
|
# @private
|
147
144
|
def root_element_included?(data)
|
148
|
-
element = data[
|
145
|
+
element = data[parsed_root_element]
|
149
146
|
element.is_a?(Hash) || element.is_a?(Array)
|
150
147
|
end
|
151
148
|
|
data/lib/her/model/paths.rb
CHANGED
@@ -19,7 +19,6 @@ module Her
|
|
19
19
|
end
|
20
20
|
|
21
21
|
module ClassMethods
|
22
|
-
|
23
22
|
# Define the primary key field that will be used to find and save records
|
24
23
|
#
|
25
24
|
# @example
|
@@ -88,13 +87,13 @@ module Her
|
|
88
87
|
# Return a custom path based on the collection path and variable parameters
|
89
88
|
#
|
90
89
|
# @private
|
91
|
-
def build_request_path(path=nil, parameters={})
|
90
|
+
def build_request_path(path = nil, parameters = {})
|
92
91
|
parameters = parameters.try(:with_indifferent_access)
|
93
92
|
|
94
93
|
unless path.is_a?(String)
|
95
94
|
parameters = path.try(:with_indifferent_access) || parameters
|
96
95
|
path =
|
97
|
-
if parameters.include?(primary_key) && parameters[primary_key] && !parameters[primary_key].
|
96
|
+
if parameters.include?(primary_key) && parameters[primary_key] && !parameters[primary_key].is_a?(Array)
|
98
97
|
resource_path.dup
|
99
98
|
else
|
100
99
|
collection_path.dup
|
@@ -117,7 +116,7 @@ module Her
|
|
117
116
|
end
|
118
117
|
|
119
118
|
# @private
|
120
|
-
def build_request_path_from_string_or_symbol(path, params={})
|
119
|
+
def build_request_path_from_string_or_symbol(path, params = {})
|
121
120
|
path.is_a?(Symbol) ? "#{build_request_path(params)}/#{path}" : path
|
122
121
|
end
|
123
122
|
end
|