her 0.7.6 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +33 -9
- data/UPGRADE.md +8 -0
- data/lib/her.rb +3 -0
- data/lib/her/api.rb +1 -2
- data/lib/her/json_api/model.rb +46 -0
- data/lib/her/middleware.rb +2 -0
- data/lib/her/middleware/json_api_parser.rb +36 -0
- data/lib/her/model.rb +3 -0
- data/lib/her/model/associations/association.rb +2 -2
- data/lib/her/model/associations/belongs_to_association.rb +2 -2
- data/lib/her/model/attributes.rb +37 -16
- data/lib/her/model/orm.rb +1 -0
- data/lib/her/model/parse.rb +1 -1
- data/lib/her/model/paths.rb +7 -2
- data/lib/her/version.rb +1 -1
- data/spec/api_spec.rb +0 -17
- data/spec/json_api/model_spec.rb +166 -0
- data/spec/middleware/json_api_parser_spec.rb +32 -0
- data/spec/model/associations_spec.rb +89 -1
- data/spec/model/attributes_spec.rb +86 -0
- data/spec/model/http_spec.rb +0 -36
- data/spec/model/orm_spec.rb +2 -0
- data/spec/model/paths_spec.rb +52 -46
- data/spec/support/macros/model_macros.rb +11 -4
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 606f0ea519a2ac25fd041636d9ef3294a26b406d
|
4
|
+
data.tar.gz: 86d7b4be4bdba0ce5e93b6410c53323478de6c7b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 177aa0aa91c294a4300ce3aed89646499df76d5b82f3230a9ce4b0e46e037ad4c2086ddb60a31d9ebb502845c26e75b6fcce069f2fb4a7166f18a3b9a60c6e48
|
7
|
+
data.tar.gz: 293fdbad4a95fc6d3126b4292b2de8624495751e2e12f4c272a0070ac41d2d832b5842e2b263cc118fd50d84f893e9cd4e9b14f51ee45edf1757ef4a74a21659
|
data/LICENSE
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c) 2012-
|
1
|
+
Copyright (c) 2012-2015 Rémi Prévost
|
2
2
|
|
3
3
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4
4
|
|
data/README.md
CHANGED
@@ -586,20 +586,44 @@ users = Users.all
|
|
586
586
|
|
587
587
|
#### JSON API support
|
588
588
|
|
589
|
-
|
590
|
-
|
589
|
+
To consume a JSON API 1.0 compliant service, it must return data in accordance with the [JSON API spec](http://jsonapi.org/). The general format
|
590
|
+
of the data is as follows:
|
591
|
+
|
592
|
+
```json
|
593
|
+
{ "data": {
|
594
|
+
"type": "developers",
|
595
|
+
"id": "6ab79c8c-ec5a-4426-ad38-8763bbede5a7",
|
596
|
+
"attributes": {
|
597
|
+
"language": "ruby",
|
598
|
+
"name": "avdi grimm",
|
599
|
+
}
|
600
|
+
}
|
601
|
+
```
|
602
|
+
|
603
|
+
Then to setup your models:
|
591
604
|
|
592
605
|
```ruby
|
593
|
-
class
|
594
|
-
include Her::Model
|
595
|
-
|
606
|
+
class Contributor
|
607
|
+
include Her::JsonApi::Model
|
608
|
+
|
609
|
+
# defaults to demodulized, pluralized class name, e.g. contributors
|
610
|
+
type :developers
|
596
611
|
end
|
612
|
+
```
|
597
613
|
|
598
|
-
|
599
|
-
# GET "/users/1", response is { "users": [{ "id": 1, "fullname": "Lindsay Fünke" }] }
|
614
|
+
Finally, you'll need to use the included JsonApiParser Her middleware:
|
600
615
|
|
601
|
-
|
602
|
-
|
616
|
+
```ruby
|
617
|
+
Her::API.setup url: 'https://my_awesome_json_api_service' do |c|
|
618
|
+
# Request
|
619
|
+
c.use FaradayMiddleware::EncodeJson
|
620
|
+
|
621
|
+
# Response
|
622
|
+
c.use Her::Middleware::JsonApiParser
|
623
|
+
|
624
|
+
# Adapter
|
625
|
+
c.use Faraday::Adapter::NetHttp
|
626
|
+
end
|
603
627
|
```
|
604
628
|
|
605
629
|
### Custom requests
|
data/UPGRADE.md
CHANGED
@@ -2,6 +2,14 @@
|
|
2
2
|
|
3
3
|
Here is a list of notable changes by release. Her follows the [Semantic Versioning](http://semver.org/) system.
|
4
4
|
|
5
|
+
## 0.8.0
|
6
|
+
|
7
|
+
- Initial support for JSONAPI [link](https://github.com/remiprev/her/pull/347)
|
8
|
+
- Fix for has_one association parsing [link](https://github.com/remiprev/her/pull/352)
|
9
|
+
- Fix for escaping path variables HT @marshall-lee [link](https://github.com/remiprev/her/pull/354)
|
10
|
+
- Fix syntax highlighting in README HT @tippenein [link](https://github.com/remiprev/her/pull/356)
|
11
|
+
- Fix associations with Active Model Serializers HT @minktom [link](https://github.com/remiprev/her/pull/359)
|
12
|
+
|
5
13
|
## 0.7.6
|
6
14
|
|
7
15
|
- Loosen restrictions on ActiveSupport and ActiveModel to accommodate security fixes [link](https://github.com/remiprev/her/commit/8ff641fcdaf14be7cc9b1a6ee6654f27f7dfa34c)
|
data/lib/her.rb
CHANGED
data/lib/her/api.rb
CHANGED
@@ -3,7 +3,7 @@ module Her
|
|
3
3
|
# so it knows where to make those requests. In Rails, this is usually done in `config/initializers/her.rb`:
|
4
4
|
class API
|
5
5
|
# @private
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :connection, :options
|
7
7
|
|
8
8
|
# Constants
|
9
9
|
FARADAY_OPTIONS = [:request, :proxy, :ssl, :builder, :url, :parallel_manager, :params, :headers, :builder_class].freeze
|
@@ -70,7 +70,6 @@ module Her
|
|
70
70
|
# end
|
71
71
|
def setup(opts={}, &blk)
|
72
72
|
opts[:url] = opts.delete(:base_uri) if opts.include?(:base_uri) # Support legacy :base_uri option
|
73
|
-
@base_uri = opts[:url]
|
74
73
|
@options = opts
|
75
74
|
|
76
75
|
faraday_options = @options.reject { |key, value| !FARADAY_OPTIONS.include?(key.to_sym) }
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Her
|
2
|
+
module JsonApi
|
3
|
+
module Model
|
4
|
+
|
5
|
+
def self.included(klass)
|
6
|
+
klass.class_eval do
|
7
|
+
include Her::Model
|
8
|
+
|
9
|
+
[:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
|
10
|
+
define_method method do |*args|
|
11
|
+
raise NoMethodError, "Her::JsonApi::Model does not support the #{method} configuration option"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
method_for :update, :patch
|
16
|
+
|
17
|
+
@type = name.demodulize.tableize
|
18
|
+
|
19
|
+
def self.parse(data)
|
20
|
+
data.fetch(:attributes).merge(data.slice(:id))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.to_params(attributes, changes={})
|
24
|
+
request_data = { type: @type }.tap { |request_body|
|
25
|
+
attrs = attributes.dup.symbolize_keys.tap { |filtered_attributes|
|
26
|
+
if her_api.options[:send_only_modified_attributes]
|
27
|
+
filtered_attributes = changes.symbolize_keys.keys.inject({}) do |hash, attribute|
|
28
|
+
hash[attribute] = filtered_attributes[attribute]
|
29
|
+
hash
|
30
|
+
end
|
31
|
+
end
|
32
|
+
}
|
33
|
+
request_body[:id] = attrs.delete(:id) if attrs[:id]
|
34
|
+
request_body[:attributes] = attrs
|
35
|
+
}
|
36
|
+
{ data: request_data }
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.type(type_name)
|
40
|
+
@type = type_name.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/her/middleware.rb
CHANGED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Her
|
2
|
+
module Middleware
|
3
|
+
# This middleware expects the resource/collection data to be contained in the `data`
|
4
|
+
# key of the JSON object
|
5
|
+
class JsonApiParser < ParseJSON
|
6
|
+
# Parse the response body
|
7
|
+
#
|
8
|
+
# @param [String] body The response body
|
9
|
+
# @return [Mixed] the parsed response
|
10
|
+
# @private
|
11
|
+
def parse(body)
|
12
|
+
json = parse_json(body)
|
13
|
+
|
14
|
+
{
|
15
|
+
:data => json[:data] || {},
|
16
|
+
:errors => json[:errors] || [],
|
17
|
+
:metadata => json[:meta] || {},
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# This method is triggered when the response has been received. It modifies
|
22
|
+
# the value of `env[:body]`.
|
23
|
+
#
|
24
|
+
# @param [Hash] env The response environment
|
25
|
+
# @private
|
26
|
+
def on_complete(env)
|
27
|
+
env[:body] = case env[:status]
|
28
|
+
when 204
|
29
|
+
parse('{}')
|
30
|
+
else
|
31
|
+
parse(env[:body])
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/her/model.rb
CHANGED
@@ -67,6 +67,9 @@ module Her
|
|
67
67
|
# Configure ActiveModel callbacks
|
68
68
|
extend ActiveModel::Callbacks
|
69
69
|
define_model_callbacks :create, :update, :save, :find, :destroy, :initialize
|
70
|
+
|
71
|
+
# Define matchers for attr? and attr= methods
|
72
|
+
define_attribute_method_matchers
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
@@ -29,7 +29,7 @@ module Her
|
|
29
29
|
if data[data_key].kind_of?(klass)
|
30
30
|
{ association[:name] => data[data_key] }
|
31
31
|
else
|
32
|
-
{ association[:name] => klass.new(data[data_key]) }
|
32
|
+
{ association[:name] => klass.new(klass.parse(data[data_key])) }
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
@@ -94,7 +94,7 @@ module Her
|
|
94
94
|
def find(id)
|
95
95
|
return nil if id.blank?
|
96
96
|
path = build_association_path lambda { "#{@parent.request_path(@params)}#{@opts[:path]}/#{id}" }
|
97
|
-
@klass.
|
97
|
+
@klass.get_resource(path, @params)
|
98
98
|
end
|
99
99
|
|
100
100
|
end
|
@@ -74,14 +74,14 @@ module Her
|
|
74
74
|
def fetch
|
75
75
|
foreign_key_value = @parent.attributes[@opts[:foreign_key].to_sym]
|
76
76
|
data_key_value = @parent.attributes[@opts[:data_key].to_sym]
|
77
|
-
return @opts[:default].try(:dup) if (@parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @params.empty?) || (
|
77
|
+
return @opts[:default].try(:dup) if (@parent.attributes.include?(@name) && @parent.attributes[@name].nil? && @params.empty?) || (foreign_key_value.blank? && data_key_value.blank?)
|
78
78
|
|
79
79
|
return @cached_result unless @params.any? || @cached_result.nil?
|
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
83
|
path = build_association_path lambda { @klass.build_request_path(path_params) }
|
84
|
-
@klass.
|
84
|
+
@klass.get_resource(path, @params).tap do |result|
|
85
85
|
@cached_result = result if @params.blank?
|
86
86
|
end
|
87
87
|
end
|
data/lib/her/model/attributes.rb
CHANGED
@@ -158,6 +158,22 @@ module Her
|
|
158
158
|
@attributes.hash
|
159
159
|
end
|
160
160
|
|
161
|
+
# Assign attribute value (ActiveModel convention method).
|
162
|
+
#
|
163
|
+
# @private
|
164
|
+
def attribute=(attribute, value)
|
165
|
+
@attributes[attribute] = nil unless @attributes.include?(attribute)
|
166
|
+
self.send(:"#{attribute}_will_change!") if @attributes[attribute] != value
|
167
|
+
@attributes[attribute] = value
|
168
|
+
end
|
169
|
+
|
170
|
+
# Check attribute value to be present (ActiveModel convention method).
|
171
|
+
#
|
172
|
+
# @private
|
173
|
+
def attribute?(attribute)
|
174
|
+
@attributes.include?(attribute) && @attributes[attribute].present?
|
175
|
+
end
|
176
|
+
|
161
177
|
module ClassMethods
|
162
178
|
# Initialize a collection of resources with raw data from an HTTP request
|
163
179
|
#
|
@@ -175,6 +191,25 @@ module Her
|
|
175
191
|
new(parse(parsed_data[:data]).merge :_metadata => parsed_data[:metadata], :_errors => parsed_data[:errors])
|
176
192
|
end
|
177
193
|
|
194
|
+
# Define attribute method matchers to automatically define them using ActiveModel's define_attribute_methods.
|
195
|
+
#
|
196
|
+
# @private
|
197
|
+
def define_attribute_method_matchers
|
198
|
+
attribute_method_suffix '='
|
199
|
+
attribute_method_suffix '?'
|
200
|
+
end
|
201
|
+
|
202
|
+
# Create a mutex for dynamically generated attribute methods or use one defined by ActiveModel.
|
203
|
+
#
|
204
|
+
# @private
|
205
|
+
def attribute_methods_mutex
|
206
|
+
@attribute_methods_mutex ||= if generated_attribute_methods.respond_to? :mu_synchronize
|
207
|
+
generated_attribute_methods
|
208
|
+
else
|
209
|
+
Mutex.new
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
178
213
|
# Define the attributes that will be used to track dirty attributes and validations
|
179
214
|
#
|
180
215
|
# @param [Array] attributes
|
@@ -184,22 +219,8 @@ module Her
|
|
184
219
|
# attributes :name, :email
|
185
220
|
# end
|
186
221
|
def attributes(*attributes)
|
187
|
-
|
188
|
-
|
189
|
-
attributes.each do |attribute|
|
190
|
-
unless method_defined?(:"#{attribute}=")
|
191
|
-
define_method("#{attribute}=") do |value|
|
192
|
-
@attributes[:"#{attribute}"] = nil unless @attributes.include?(:"#{attribute}")
|
193
|
-
self.send(:"#{attribute}_will_change!") if @attributes[:"#{attribute}"] != value
|
194
|
-
@attributes[:"#{attribute}"] = value
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
|
-
unless method_defined?(:"#{attribute}?")
|
199
|
-
define_method("#{attribute}?") do
|
200
|
-
@attributes.include?(:"#{attribute}") && @attributes[:"#{attribute}"].present?
|
201
|
-
end
|
202
|
-
end
|
222
|
+
attribute_methods_mutex.synchronize do
|
223
|
+
define_attribute_methods attributes
|
203
224
|
end
|
204
225
|
end
|
205
226
|
|
data/lib/her/model/orm.rb
CHANGED
data/lib/her/model/parse.rb
CHANGED
@@ -169,7 +169,7 @@ module Her
|
|
169
169
|
#
|
170
170
|
# @private
|
171
171
|
def extract_array(request_data)
|
172
|
-
if active_model_serializers_format? || json_api_format?
|
172
|
+
if request_data[:data].is_a?(Hash) && (active_model_serializers_format? || json_api_format?)
|
173
173
|
request_data[:data][pluralized_parsed_root_element]
|
174
174
|
else
|
175
175
|
request_data[:data]
|
data/lib/her/model/paths.rb
CHANGED
@@ -106,8 +106,13 @@ module Her
|
|
106
106
|
|
107
107
|
path.gsub(/:([\w_]+)/) do
|
108
108
|
# Look for :key or :_key, otherwise raise an exception
|
109
|
-
|
110
|
-
parameters.delete(
|
109
|
+
key = $1.to_sym
|
110
|
+
value = parameters.delete(key) || parameters.delete(:"_#{key}")
|
111
|
+
if value
|
112
|
+
Faraday::Utils.escape value
|
113
|
+
else
|
114
|
+
raise(Her::Errors::PathError.new("Missing :_#{$1} parameter to build the request path. Path is `#{path}`. Parameters are `#{parameters.symbolize_keys.inspect}`.", $1))
|
115
|
+
end
|
111
116
|
end
|
112
117
|
end
|
113
118
|
|
data/lib/her/version.rb
CHANGED
data/spec/api_spec.rb
CHANGED
@@ -5,24 +5,7 @@ describe Her::API do
|
|
5
5
|
subject { Her::API.new }
|
6
6
|
|
7
7
|
context "initialization" do
|
8
|
-
describe ".setup" do
|
9
|
-
it "creates a default connection" do
|
10
|
-
Her::API.setup :url => "https://api.example.com"
|
11
|
-
Her::API.default_api.base_uri.should == "https://api.example.com"
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
8
|
describe "#setup" do
|
16
|
-
context "when using :url option" do
|
17
|
-
before { subject.setup :url => "https://api.example.com" }
|
18
|
-
its(:base_uri) { should == "https://api.example.com" }
|
19
|
-
end
|
20
|
-
|
21
|
-
context "when using the legacy :base_uri option" do
|
22
|
-
before { subject.setup :base_uri => "https://api.example.com" }
|
23
|
-
its(:base_uri) { should == "https://api.example.com" }
|
24
|
-
end
|
25
|
-
|
26
9
|
context "when setting custom middleware" do
|
27
10
|
before do
|
28
11
|
class Foo; end;
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Her::JsonApi::Model do
|
4
|
+
before do
|
5
|
+
Her::API.setup :url => "https://api.example.com" do |connection|
|
6
|
+
connection.use Her::Middleware::JsonApiParser
|
7
|
+
connection.adapter :test do |stub|
|
8
|
+
stub.get("/users/1") do |env|
|
9
|
+
[
|
10
|
+
200,
|
11
|
+
{},
|
12
|
+
{
|
13
|
+
data: {
|
14
|
+
id: 1,
|
15
|
+
type: 'users',
|
16
|
+
attributes: {
|
17
|
+
name: "Roger Federer",
|
18
|
+
},
|
19
|
+
}
|
20
|
+
|
21
|
+
}.to_json
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
stub.get("/users") do |env|
|
26
|
+
[
|
27
|
+
200,
|
28
|
+
{},
|
29
|
+
{
|
30
|
+
data: [
|
31
|
+
{
|
32
|
+
id: 1,
|
33
|
+
type: 'users',
|
34
|
+
attributes: {
|
35
|
+
name: "Roger Federer",
|
36
|
+
},
|
37
|
+
},
|
38
|
+
{
|
39
|
+
id: 2,
|
40
|
+
type: 'users',
|
41
|
+
attributes: {
|
42
|
+
name: "Kei Nishikori",
|
43
|
+
},
|
44
|
+
}
|
45
|
+
]
|
46
|
+
}.to_json
|
47
|
+
]
|
48
|
+
end
|
49
|
+
|
50
|
+
stub.post("/users", data: {
|
51
|
+
type: 'users',
|
52
|
+
attributes: {
|
53
|
+
name: "Jeremy Lin",
|
54
|
+
},
|
55
|
+
}) do |env|
|
56
|
+
[
|
57
|
+
201,
|
58
|
+
{},
|
59
|
+
{
|
60
|
+
data: {
|
61
|
+
id: 3,
|
62
|
+
type: 'users',
|
63
|
+
attributes: {
|
64
|
+
name: 'Jeremy Lin',
|
65
|
+
},
|
66
|
+
}
|
67
|
+
|
68
|
+
}.to_json
|
69
|
+
]
|
70
|
+
end
|
71
|
+
|
72
|
+
stub.patch("/users/1", data: {
|
73
|
+
type: 'users',
|
74
|
+
id: 1,
|
75
|
+
attributes: {
|
76
|
+
name: "Fed GOAT",
|
77
|
+
},
|
78
|
+
}) do |env|
|
79
|
+
[
|
80
|
+
200,
|
81
|
+
{},
|
82
|
+
{
|
83
|
+
data: {
|
84
|
+
id: 1,
|
85
|
+
type: 'users',
|
86
|
+
attributes: {
|
87
|
+
name: 'Fed GOAT',
|
88
|
+
},
|
89
|
+
}
|
90
|
+
|
91
|
+
}.to_json
|
92
|
+
]
|
93
|
+
end
|
94
|
+
|
95
|
+
stub.delete("/users/1") { |env|
|
96
|
+
[ 204, {}, {}, ]
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
spawn_model("Foo::User", type: Her::JsonApi::Model)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'allows configuration of type' do
|
106
|
+
spawn_model("Foo::Bar", type: Her::JsonApi::Model) do
|
107
|
+
type :foobars
|
108
|
+
end
|
109
|
+
|
110
|
+
expect(Foo::Bar.instance_variable_get('@type')).to eql('foobars')
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'finds models by id' do
|
114
|
+
user = Foo::User.find(1)
|
115
|
+
expect(user.attributes).to eql(
|
116
|
+
'id' => 1,
|
117
|
+
'name' => 'Roger Federer',
|
118
|
+
)
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'finds a collection of models' do
|
122
|
+
users = Foo::User.all
|
123
|
+
expect(users.map(&:attributes)).to match_array([
|
124
|
+
{
|
125
|
+
'id' => 1,
|
126
|
+
'name' => 'Roger Federer',
|
127
|
+
},
|
128
|
+
{
|
129
|
+
'id' => 2,
|
130
|
+
'name' => 'Kei Nishikori',
|
131
|
+
}
|
132
|
+
])
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'creates a Foo::User' do
|
136
|
+
user = Foo::User.new(name: 'Jeremy Lin')
|
137
|
+
user.save
|
138
|
+
expect(user.attributes).to eql(
|
139
|
+
'id' => 3,
|
140
|
+
'name' => 'Jeremy Lin',
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'updates a Foo::User' do
|
145
|
+
user = Foo::User.find(1)
|
146
|
+
user.name = 'Fed GOAT'
|
147
|
+
user.save
|
148
|
+
expect(user.attributes).to eql(
|
149
|
+
'id' => 1,
|
150
|
+
'name' => 'Fed GOAT',
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
it 'destroys a Foo::User' do
|
155
|
+
user = Foo::User.find(1)
|
156
|
+
expect(user.destroy).to be_destroyed
|
157
|
+
end
|
158
|
+
|
159
|
+
context 'undefined methods' do
|
160
|
+
it 'removes methods that are not compatible with json api' do
|
161
|
+
[:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
|
162
|
+
expect { Foo::User.new.send(method, :foo) }.to raise_error NoMethodError, "Her::JsonApi::Model does not support the #{method} configuration option"
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Her::Middleware::JsonApiParser do
|
5
|
+
subject { described_class.new }
|
6
|
+
|
7
|
+
context "with valid JSON body" do
|
8
|
+
let(:body) { '{"data": {"type": "foo", "id": "bar", "attributes": {"baz": "qux"} }, "meta": {"api": "json api"} }' }
|
9
|
+
let(:env) { { body: body } }
|
10
|
+
|
11
|
+
it "parses body as json" do
|
12
|
+
subject.on_complete(env)
|
13
|
+
env.fetch(:body).tap do |json|
|
14
|
+
expect(json[:data]).to eql(
|
15
|
+
:type => "foo",
|
16
|
+
:id => "bar",
|
17
|
+
:attributes => { :baz => "qux" }
|
18
|
+
)
|
19
|
+
expect(json[:errors]).to eql([])
|
20
|
+
expect(json[:metadata]).to eql(:api => "json api")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
#context "with invalid JSON body" do
|
26
|
+
# let(:body) { '"foo"' }
|
27
|
+
# it 'ensures that invalid JSON throws an exception' do
|
28
|
+
# expect { subject.parse(body) }.to raise_error(Her::Errors::ParseError, 'Response from the API must behave like a Hash or an Array (last JSON response was "\"foo\"")')
|
29
|
+
# end
|
30
|
+
#end
|
31
|
+
|
32
|
+
end
|
@@ -76,7 +76,7 @@ describe Her::Model::Associations do
|
|
76
76
|
describe "associations accessor" do
|
77
77
|
subject { Class.new(Foo::User).associations }
|
78
78
|
its(:object_id) { should_not eql Foo::User.associations.object_id }
|
79
|
-
|
79
|
+
its([:has_many]) { should eql [{ :name => :comments, :data_key => :comments, :default => [], :class_name => "Post", :path => "/comments", :inverse_of => nil }] }
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
@@ -132,6 +132,7 @@ describe Her::Model::Associations do
|
|
132
132
|
|
133
133
|
@user_with_included_data = Foo::User.find(1)
|
134
134
|
@user_without_included_data = Foo::User.find(2)
|
135
|
+
@user_without_organization_and_not_persisted = Foo::User.new(organization_id: nil, name: "Katlin Fünke")
|
135
136
|
end
|
136
137
|
|
137
138
|
let(:user_with_included_data_after_create) { Foo::User.create }
|
@@ -210,6 +211,10 @@ describe Her::Model::Associations do
|
|
210
211
|
@user_without_included_data.organization.name.should == "Bluth Company"
|
211
212
|
end
|
212
213
|
|
214
|
+
it "returns nil if the foreign key is nil" do
|
215
|
+
@user_without_organization_and_not_persisted.organization.should be_nil
|
216
|
+
end
|
217
|
+
|
213
218
|
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
214
219
|
@user_with_included_data.organization.where(:foo_id => 1).name.should == "Bluth Company Foo"
|
215
220
|
end
|
@@ -231,6 +236,7 @@ describe Her::Model::Associations do
|
|
231
236
|
|
232
237
|
it "fetches data with the specified id when calling find" do
|
233
238
|
comment = @user_without_included_data.comments.find(5)
|
239
|
+
comment.should be_a(Foo::Comment)
|
234
240
|
comment.id.should eq(5)
|
235
241
|
end
|
236
242
|
|
@@ -265,6 +271,88 @@ describe Her::Model::Associations do
|
|
265
271
|
end
|
266
272
|
end
|
267
273
|
|
274
|
+
context "handling associations with details in active_model_serializers format" do
|
275
|
+
before do
|
276
|
+
Her::API.setup :url => "https://api.example.com" do |builder|
|
277
|
+
builder.use Her::Middleware::FirstLevelParseJSON
|
278
|
+
builder.use Faraday::Request::UrlEncoded
|
279
|
+
builder.adapter :test do |stub|
|
280
|
+
stub.get("/users/1") { |env| [200, {}, { :user => { :id => 1, :name => "Tobias Fünke", :comments => [{ :id => 2, :body => "Tobias, you blow hard!", :user_id => 1 }, { :id => 3, :body => "I wouldn't mind kissing that man between the cheeks, so to speak", :user_id => 1 }], :role => { :id => 1, :body => "Admin" }, :organization => { :id => 1, :name => "Bluth Company" }, :organization_id => 1 } }.to_json] }
|
281
|
+
stub.get("/users/2") { |env| [200, {}, { :user => { :id => 2, :name => "Lindsay Fünke", :organization_id => 1 } }.to_json] }
|
282
|
+
stub.get("/users/1/comments") { |env| [200, {}, { :comments => [{ :id => 4, :body => "They're having a FIRESALE?" }] }.to_json] }
|
283
|
+
stub.get("/users/2/comments") { |env| [200, {}, { :comments => [{ :id => 4, :body => "They're having a FIRESALE?" }, { :id => 5, :body => "Is this the tiny town from Footloose?" }] }.to_json] }
|
284
|
+
stub.get("/users/2/comments/5") { |env| [200, {}, { :comment => { :id => 5, :body => "Is this the tiny town from Footloose?" } }.to_json] }
|
285
|
+
stub.get("/organizations/1") { |env| [200, {}, { :organization => { :id => 1, :name => "Bluth Company Foo" } }.to_json] }
|
286
|
+
end
|
287
|
+
end
|
288
|
+
spawn_model "Foo::User" do
|
289
|
+
parse_root_in_json true, :format => :active_model_serializers
|
290
|
+
has_many :comments, class_name: "Foo::Comment"
|
291
|
+
belongs_to :organization
|
292
|
+
end
|
293
|
+
spawn_model "Foo::Comment" do
|
294
|
+
belongs_to :user
|
295
|
+
parse_root_in_json true, :format => :active_model_serializers
|
296
|
+
end
|
297
|
+
spawn_model "Foo::Organization" do
|
298
|
+
parse_root_in_json true, :format => :active_model_serializers
|
299
|
+
end
|
300
|
+
|
301
|
+
@user_with_included_data = Foo::User.find(1)
|
302
|
+
@user_without_included_data = Foo::User.find(2)
|
303
|
+
end
|
304
|
+
|
305
|
+
it "maps an array of included data through has_many" do
|
306
|
+
@user_with_included_data.comments.first.should be_a(Foo::Comment)
|
307
|
+
@user_with_included_data.comments.length.should == 2
|
308
|
+
@user_with_included_data.comments.first.id.should == 2
|
309
|
+
@user_with_included_data.comments.first.body.should == "Tobias, you blow hard!"
|
310
|
+
end
|
311
|
+
|
312
|
+
it "does not refetch the parents models data if they have been fetched before" do
|
313
|
+
@user_with_included_data.comments.first.user.object_id.should == @user_with_included_data.object_id
|
314
|
+
end
|
315
|
+
|
316
|
+
it "fetches data that was not included through has_many" do
|
317
|
+
@user_without_included_data.comments.first.should be_a(Foo::Comment)
|
318
|
+
@user_without_included_data.comments.length.should == 2
|
319
|
+
@user_without_included_data.comments.first.id.should == 4
|
320
|
+
@user_without_included_data.comments.first.body.should == "They're having a FIRESALE?"
|
321
|
+
end
|
322
|
+
|
323
|
+
it "fetches has_many data even if it was included, only if called with parameters" do
|
324
|
+
@user_with_included_data.comments.where(:foo_id => 1).length.should == 1
|
325
|
+
end
|
326
|
+
|
327
|
+
it "maps an array of included data through belongs_to" do
|
328
|
+
@user_with_included_data.organization.should be_a(Foo::Organization)
|
329
|
+
@user_with_included_data.organization.id.should == 1
|
330
|
+
@user_with_included_data.organization.name.should == "Bluth Company"
|
331
|
+
end
|
332
|
+
|
333
|
+
it "fetches data that was not included through belongs_to" do
|
334
|
+
@user_without_included_data.organization.should be_a(Foo::Organization)
|
335
|
+
@user_without_included_data.organization.id.should == 1
|
336
|
+
@user_without_included_data.organization.name.should == "Bluth Company Foo"
|
337
|
+
end
|
338
|
+
|
339
|
+
it "fetches belongs_to data even if it was included, only if called with parameters" do
|
340
|
+
@user_with_included_data.organization.where(:foo_id => 1).name.should == "Bluth Company Foo"
|
341
|
+
end
|
342
|
+
|
343
|
+
it "fetches data with the specified id when calling find" do
|
344
|
+
comment = @user_without_included_data.comments.find(5)
|
345
|
+
comment.should be_a(Foo::Comment)
|
346
|
+
comment.id.should eq(5)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'includes has_many relationships in params by default' do
|
350
|
+
params = @user_with_included_data.to_params
|
351
|
+
params[:comments].should be_kind_of(Array)
|
352
|
+
params[:comments].length.should eq(2)
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
268
356
|
context "handling associations with details" do
|
269
357
|
before do
|
270
358
|
Her::API.setup :url => "https://api.example.com" do |builder|
|
@@ -299,5 +299,91 @@ describe Her::Model::Attributes do
|
|
299
299
|
user.assign_attributes(fullname: 'Tobias Fünke')
|
300
300
|
user.fullname?.should be_truthy
|
301
301
|
end
|
302
|
+
|
303
|
+
context "when attribute methods are already defined" do
|
304
|
+
before do
|
305
|
+
class AbstractUser
|
306
|
+
attr_accessor :fullname
|
307
|
+
|
308
|
+
def fullname?
|
309
|
+
@fullname.present?
|
310
|
+
end
|
311
|
+
end
|
312
|
+
@spawned_models << :AbstractUser
|
313
|
+
|
314
|
+
spawn_model 'Foo::User', super_class: AbstractUser do
|
315
|
+
attributes :fullname
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
it "overrides getter method" do
|
320
|
+
Foo::User.generated_attribute_methods.instance_methods.should include(:fullname)
|
321
|
+
end
|
322
|
+
|
323
|
+
it "overrides setter method" do
|
324
|
+
Foo::User.generated_attribute_methods.instance_methods.should include(:fullname=)
|
325
|
+
end
|
326
|
+
|
327
|
+
it "overrides predicate method" do
|
328
|
+
Foo::User.generated_attribute_methods.instance_methods.should include(:fullname?)
|
329
|
+
end
|
330
|
+
|
331
|
+
it "defines setter that affects @attributes" do
|
332
|
+
user = Foo::User.new
|
333
|
+
user.fullname = 'Tobias Fünke'
|
334
|
+
user.attributes[:fullname].should eq('Tobias Fünke')
|
335
|
+
end
|
336
|
+
|
337
|
+
it "defines getter that reads @attributes" do
|
338
|
+
user = Foo::User.new
|
339
|
+
user.attributes[:fullname] = 'Tobias Fünke'
|
340
|
+
user.fullname.should eq('Tobias Fünke')
|
341
|
+
end
|
342
|
+
|
343
|
+
it "defines predicate that reads @attributes" do
|
344
|
+
user = Foo::User.new
|
345
|
+
user.fullname?.should be_falsey
|
346
|
+
user.attributes[:fullname] = 'Tobias Fünke'
|
347
|
+
user.fullname?.should be_truthy
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
if ActiveModel::VERSION::MAJOR < 4
|
352
|
+
it "creates a new mutex" do
|
353
|
+
expect(Mutex).to receive(:new).once.and_call_original
|
354
|
+
spawn_model 'Foo::User' do
|
355
|
+
attributes :fullname
|
356
|
+
end
|
357
|
+
Foo::User.attribute_methods_mutex.should_not eq(Foo::User.generated_attribute_methods)
|
358
|
+
end
|
359
|
+
|
360
|
+
it "works well with Module#synchronize monkey patched by ActiveSupport" do
|
361
|
+
Module.class_eval do
|
362
|
+
def synchronize(*args)
|
363
|
+
raise 'gotcha!'
|
364
|
+
end
|
365
|
+
end
|
366
|
+
expect(Mutex).to receive(:new).once.and_call_original
|
367
|
+
spawn_model 'Foo::User' do
|
368
|
+
attributes :fullname
|
369
|
+
end
|
370
|
+
Foo::User.attribute_methods_mutex.should_not eq(Foo::User.generated_attribute_methods)
|
371
|
+
Module.class_eval do
|
372
|
+
undef :synchronize
|
373
|
+
end
|
374
|
+
end
|
375
|
+
else
|
376
|
+
it "uses ActiveModel's mutex" do
|
377
|
+
Foo::User.attribute_methods_mutex.should eq(Foo::User.generated_attribute_methods)
|
378
|
+
end
|
379
|
+
end
|
380
|
+
|
381
|
+
it "uses a mutex" do
|
382
|
+
spawn_model 'Foo::User'
|
383
|
+
expect(Foo::User.attribute_methods_mutex).to receive(:synchronize).once.and_call_original
|
384
|
+
Foo::User.class_eval do
|
385
|
+
attributes :fullname, :documents
|
386
|
+
end
|
387
|
+
end
|
302
388
|
end
|
303
389
|
end
|
data/spec/model/http_spec.rb
CHANGED
@@ -12,42 +12,6 @@ describe Her::Model::HTTP do
|
|
12
12
|
Her::API.setup :url => "https://api.example.com"
|
13
13
|
end
|
14
14
|
|
15
|
-
context "when binding a model to an instance of Her::API" do
|
16
|
-
before { Foo::User.uses_api api1 }
|
17
|
-
subject { Foo::User.her_api }
|
18
|
-
its(:base_uri) { should == "https://api1.example.com" }
|
19
|
-
end
|
20
|
-
|
21
|
-
context "when binding a model directly to Her::API" do
|
22
|
-
before { spawn_model "Foo::User" }
|
23
|
-
subject { Foo::User.her_api }
|
24
|
-
its(:base_uri) { should == "https://api.example.com" }
|
25
|
-
end
|
26
|
-
|
27
|
-
context "when using a proc for uses_api" do
|
28
|
-
before do
|
29
|
-
Foo::User.uses_api lambda { Her::API.new :url => 'http://api-lambda.example.com' }
|
30
|
-
end
|
31
|
-
|
32
|
-
specify { Foo::User.her_api.base_uri.should == 'http://api-lambda.example.com' }
|
33
|
-
end
|
34
|
-
|
35
|
-
context "when binding two models to two different instances of Her::API" do
|
36
|
-
before do
|
37
|
-
Foo::User.uses_api api1
|
38
|
-
Foo::Comment.uses_api api2
|
39
|
-
end
|
40
|
-
|
41
|
-
specify { Foo::User.her_api.base_uri.should == "https://api1.example.com" }
|
42
|
-
specify { Foo::Comment.her_api.base_uri.should == "https://api2.example.com" }
|
43
|
-
end
|
44
|
-
|
45
|
-
context "binding one model to Her::API and another one to an instance of Her::API" do
|
46
|
-
before { Foo::Comment.uses_api api2 }
|
47
|
-
specify { Foo::User.her_api.base_uri.should == "https://api.example.com" }
|
48
|
-
specify { Foo::Comment.her_api.base_uri.should == "https://api2.example.com" }
|
49
|
-
end
|
50
|
-
|
51
15
|
context "when binding a model to its superclass' her_api" do
|
52
16
|
before do
|
53
17
|
spawn_model "Foo::Superclass"
|
data/spec/model/orm_spec.rb
CHANGED
@@ -49,10 +49,12 @@ describe Her::Model::ORM do
|
|
49
49
|
it "handles new resource" do
|
50
50
|
@new_user = Foo::User.new(:fullname => "Tobias Fünke")
|
51
51
|
@new_user.new?.should be_truthy
|
52
|
+
@new_user.new_record?.should be_truthy
|
52
53
|
@new_user.fullname.should == "Tobias Fünke"
|
53
54
|
|
54
55
|
@existing_user = Foo::User.find(1)
|
55
56
|
@existing_user.new?.should be_falsey
|
57
|
+
@existing_user.new_record?.should be_falsey
|
56
58
|
end
|
57
59
|
|
58
60
|
it 'handles new resource with custom primary key' do
|
data/spec/model/paths_spec.rb
CHANGED
@@ -8,60 +8,66 @@ describe Her::Model::Paths do
|
|
8
8
|
spawn_model "Foo::User"
|
9
9
|
end
|
10
10
|
|
11
|
-
describe "#
|
11
|
+
describe "#request_path" do
|
12
12
|
it "builds paths with defaults" do
|
13
|
-
Foo::User.
|
14
|
-
Foo::User.
|
15
|
-
Foo::User.
|
13
|
+
Foo::User.new(:id => "foo").request_path.should == "users/foo"
|
14
|
+
Foo::User.new(:id => nil).request_path.should == "users"
|
15
|
+
Foo::User.new().request_path.should == "users"
|
16
16
|
end
|
17
17
|
|
18
18
|
it "builds paths with custom collection path" do
|
19
19
|
Foo::User.collection_path "/utilisateurs"
|
20
|
-
Foo::User.
|
21
|
-
Foo::User.
|
20
|
+
Foo::User.new(:id => "foo").request_path.should == "/utilisateurs/foo"
|
21
|
+
Foo::User.new().request_path.should == "/utilisateurs"
|
22
22
|
end
|
23
23
|
|
24
24
|
it "builds paths with custom relative collection path" do
|
25
25
|
Foo::User.collection_path "utilisateurs"
|
26
|
-
Foo::User.
|
27
|
-
Foo::User.
|
26
|
+
Foo::User.new(:id => "foo").request_path.should == "utilisateurs/foo"
|
27
|
+
Foo::User.new().request_path.should == "utilisateurs"
|
28
28
|
end
|
29
29
|
|
30
30
|
it "builds paths with custom collection path with multiple variables" do
|
31
31
|
Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
|
32
32
|
|
33
|
-
Foo::User.
|
34
|
-
Foo::User.
|
33
|
+
Foo::User.new(:id => "foo").request_path(:_organization_id => "acme").should == "/organizations/acme/utilisateurs/foo"
|
34
|
+
Foo::User.new().request_path(:_organization_id => "acme").should == "/organizations/acme/utilisateurs"
|
35
35
|
|
36
|
-
Foo::User.
|
37
|
-
Foo::User.
|
36
|
+
Foo::User.new(:id => "foo", :organization_id => "acme").request_path.should == "/organizations/acme/utilisateurs/foo"
|
37
|
+
Foo::User.new(:organization_id => "acme").request_path.should == "/organizations/acme/utilisateurs"
|
38
38
|
end
|
39
39
|
|
40
40
|
it "builds paths with custom relative collection path with multiple variables" do
|
41
41
|
Foo::User.collection_path "organizations/:organization_id/utilisateurs"
|
42
42
|
|
43
|
-
Foo::User.
|
44
|
-
Foo::User.
|
43
|
+
Foo::User.new(:id => "foo").request_path(:_organization_id => "acme").should == "organizations/acme/utilisateurs/foo"
|
44
|
+
Foo::User.new().request_path(:_organization_id => "acme").should == "organizations/acme/utilisateurs"
|
45
45
|
|
46
|
-
Foo::User.
|
47
|
-
Foo::User.
|
46
|
+
Foo::User.new(:id => "foo", :organization_id => "acme").request_path.should == "organizations/acme/utilisateurs/foo"
|
47
|
+
Foo::User.new(:organization_id => "acme").request_path.should == "organizations/acme/utilisateurs"
|
48
48
|
end
|
49
49
|
|
50
50
|
it "builds paths with custom item path" do
|
51
51
|
Foo::User.resource_path "/utilisateurs/:id"
|
52
|
-
Foo::User.
|
53
|
-
Foo::User.
|
52
|
+
Foo::User.new(:id => "foo").request_path.should == "/utilisateurs/foo"
|
53
|
+
Foo::User.new().request_path.should == "users"
|
54
54
|
end
|
55
55
|
|
56
56
|
it "builds paths with custom relative item path" do
|
57
57
|
Foo::User.resource_path "utilisateurs/:id"
|
58
|
-
Foo::User.
|
59
|
-
Foo::User.
|
58
|
+
Foo::User.new(:id => "foo").request_path.should == "utilisateurs/foo"
|
59
|
+
Foo::User.new().request_path.should == "users"
|
60
60
|
end
|
61
61
|
|
62
62
|
it "raises exceptions when building a path without required custom variables" do
|
63
63
|
Foo::User.collection_path "/organizations/:organization_id/utilisateurs"
|
64
|
-
expect { Foo::User.
|
64
|
+
expect { Foo::User.new(:id => "foo").request_path }.to raise_error(Her::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `/organizations/:organization_id/utilisateurs/:id`. Parameters are `{:id=>\"foo\"}`.")
|
65
|
+
end
|
66
|
+
|
67
|
+
it "escapes the variable values" do
|
68
|
+
Foo::User.collection_path "organizations/:organization_id/utilisateurs"
|
69
|
+
Foo::User.new(:id => "Привет").request_path(:_organization_id => 'лол').should == "organizations/%D0%BB%D0%BE%D0%BB/utilisateurs/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82"
|
70
|
+
Foo::User.new(:organization_id => 'лол', :id => "Привет").request_path.should == "organizations/%D0%BB%D0%BE%D0%BB/utilisateurs/%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82"
|
65
71
|
end
|
66
72
|
end
|
67
73
|
end
|
@@ -71,56 +77,56 @@ describe Her::Model::Paths do
|
|
71
77
|
spawn_model "Foo::AdminUser"
|
72
78
|
end
|
73
79
|
|
74
|
-
describe "#
|
80
|
+
describe "#request_path" do
|
75
81
|
it "builds paths with defaults" do
|
76
|
-
Foo::AdminUser.
|
77
|
-
Foo::AdminUser.
|
82
|
+
Foo::AdminUser.new(:id => "foo").request_path.should == "admin_users/foo"
|
83
|
+
Foo::AdminUser.new().request_path.should == "admin_users"
|
78
84
|
end
|
79
85
|
|
80
86
|
it "builds paths with custom collection path" do
|
81
87
|
Foo::AdminUser.collection_path "/users"
|
82
|
-
Foo::AdminUser.
|
83
|
-
Foo::AdminUser.
|
88
|
+
Foo::AdminUser.new(:id => "foo").request_path.should == "/users/foo"
|
89
|
+
Foo::AdminUser.new().request_path.should == "/users"
|
84
90
|
end
|
85
91
|
|
86
92
|
it "builds paths with custom relative collection path" do
|
87
93
|
Foo::AdminUser.collection_path "users"
|
88
|
-
Foo::AdminUser.
|
89
|
-
Foo::AdminUser.
|
94
|
+
Foo::AdminUser.new(:id => "foo").request_path.should == "users/foo"
|
95
|
+
Foo::AdminUser.new().request_path.should == "users"
|
90
96
|
end
|
91
97
|
|
92
98
|
it "builds paths with custom collection path with multiple variables" do
|
93
99
|
Foo::AdminUser.collection_path "/organizations/:organization_id/users"
|
94
|
-
Foo::AdminUser.
|
95
|
-
Foo::AdminUser.
|
100
|
+
Foo::AdminUser.new(:id => "foo").request_path(:_organization_id => "acme").should == "/organizations/acme/users/foo"
|
101
|
+
Foo::AdminUser.new().request_path(:_organization_id => "acme").should == "/organizations/acme/users"
|
96
102
|
end
|
97
103
|
|
98
104
|
it "builds paths with custom relative collection path with multiple variables" do
|
99
105
|
Foo::AdminUser.collection_path "organizations/:organization_id/users"
|
100
|
-
Foo::AdminUser.
|
101
|
-
Foo::AdminUser.
|
106
|
+
Foo::AdminUser.new(:id => "foo").request_path(:_organization_id => "acme").should == "organizations/acme/users/foo"
|
107
|
+
Foo::AdminUser.new().request_path(:_organization_id => "acme").should == "organizations/acme/users"
|
102
108
|
end
|
103
109
|
|
104
110
|
it "builds paths with custom item path" do
|
105
111
|
Foo::AdminUser.resource_path "/users/:id"
|
106
|
-
Foo::AdminUser.
|
107
|
-
Foo::AdminUser.
|
112
|
+
Foo::AdminUser.new(:id => "foo").request_path.should == "/users/foo"
|
113
|
+
Foo::AdminUser.new().request_path.should == "admin_users"
|
108
114
|
end
|
109
115
|
|
110
116
|
it "builds paths with custom relative item path" do
|
111
117
|
Foo::AdminUser.resource_path "users/:id"
|
112
|
-
Foo::AdminUser.
|
113
|
-
Foo::AdminUser.
|
118
|
+
Foo::AdminUser.new(:id => "foo").request_path.should == "users/foo"
|
119
|
+
Foo::AdminUser.new().request_path.should == "admin_users"
|
114
120
|
end
|
115
121
|
|
116
122
|
it "raises exceptions when building a path without required custom variables" do
|
117
123
|
Foo::AdminUser.collection_path "/organizations/:organization_id/users"
|
118
|
-
expect { Foo::AdminUser.
|
124
|
+
expect { Foo::AdminUser.new(:id => "foo").request_path }.to raise_error(Her::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `/organizations/:organization_id/users/:id`. Parameters are `{:id=>\"foo\"}`.")
|
119
125
|
end
|
120
126
|
|
121
127
|
it "raises exceptions when building a relative path without required custom variables" do
|
122
128
|
Foo::AdminUser.collection_path "organizations/:organization_id/users"
|
123
|
-
expect { Foo::AdminUser.
|
129
|
+
expect { Foo::AdminUser.new(:id => "foo").request_path }.to raise_error(Her::Errors::PathError, "Missing :_organization_id parameter to build the request path. Path is `organizations/:organization_id/users/:id`. Parameters are `{:id=>\"foo\"}`.")
|
124
130
|
end
|
125
131
|
end
|
126
132
|
end
|
@@ -152,10 +158,10 @@ describe Her::Model::Paths do
|
|
152
158
|
spawn_model "Foo::User"
|
153
159
|
end
|
154
160
|
|
155
|
-
describe "#
|
161
|
+
describe "#request_path" do
|
156
162
|
it "builds paths with defaults" do
|
157
|
-
Foo::User.
|
158
|
-
Foo::User.
|
163
|
+
Foo::User.new(:id => "foo").request_path.should == "users/foo"
|
164
|
+
Foo::User.new.request_path.should == "users"
|
159
165
|
end
|
160
166
|
end
|
161
167
|
end
|
@@ -173,15 +179,15 @@ describe Her::Model::Paths do
|
|
173
179
|
end
|
174
180
|
end
|
175
181
|
|
176
|
-
describe '#
|
182
|
+
describe '#request_path' do
|
177
183
|
it 'uses the correct primary key attribute' do
|
178
|
-
User.
|
179
|
-
User.
|
184
|
+
User.new(:UserId => 'foo').request_path.should == 'users/foo'
|
185
|
+
User.new(:id => 'foo').request_path.should == 'users'
|
180
186
|
end
|
181
187
|
|
182
188
|
it 'replaces :id with the appropriate primary key' do
|
183
|
-
Customer.
|
184
|
-
Customer.
|
189
|
+
Customer.new(:customer_id => 'joe').request_path.should == 'customers/joe'
|
190
|
+
Customer.new(:id => 'joe').request_path.should == 'customers'
|
185
191
|
end
|
186
192
|
end
|
187
193
|
end
|
@@ -3,21 +3,28 @@ module Her
|
|
3
3
|
module Macros
|
4
4
|
module ModelMacros
|
5
5
|
# Create a class and automatically inject Her::Model into it
|
6
|
-
def spawn_model(klass, &block)
|
6
|
+
def spawn_model(klass, options={}, &block)
|
7
|
+
super_class = options[:super_class]
|
8
|
+
model_type = options[:type] || Her::Model
|
9
|
+
new_class = if super_class
|
10
|
+
Class.new(super_class)
|
11
|
+
else
|
12
|
+
Class.new
|
13
|
+
end
|
7
14
|
if klass =~ /::/
|
8
15
|
base, submodel = klass.split(/::/).map{ |s| s.to_sym }
|
9
16
|
Object.const_set(base, Module.new) unless Object.const_defined?(base)
|
10
17
|
Object.const_get(base).module_eval do
|
11
18
|
remove_const submodel if constants.map(&:to_sym).include?(submodel)
|
12
|
-
submodel = const_set(submodel,
|
13
|
-
submodel.send(:include,
|
19
|
+
submodel = const_set(submodel, new_class)
|
20
|
+
submodel.send(:include, model_type)
|
14
21
|
submodel.class_eval(&block) if block_given?
|
15
22
|
end
|
16
23
|
|
17
24
|
@spawned_models << base
|
18
25
|
else
|
19
26
|
Object.instance_eval { remove_const klass } if Object.const_defined?(klass)
|
20
|
-
Object.const_set(klass, Class.new).send(:include,
|
27
|
+
Object.const_set(klass, Class.new).send(:include, model_type)
|
21
28
|
Object.const_get(klass).class_eval(&block) if block_given?
|
22
29
|
|
23
30
|
@spawned_models << klass.to_sym
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: her
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.8.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémi Prévost
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake
|
@@ -180,9 +180,11 @@ files:
|
|
180
180
|
- lib/her/api.rb
|
181
181
|
- lib/her/collection.rb
|
182
182
|
- lib/her/errors.rb
|
183
|
+
- lib/her/json_api/model.rb
|
183
184
|
- lib/her/middleware.rb
|
184
185
|
- lib/her/middleware/accept_json.rb
|
185
186
|
- lib/her/middleware/first_level_parse_json.rb
|
187
|
+
- lib/her/middleware/json_api_parser.rb
|
186
188
|
- lib/her/middleware/parse_json.rb
|
187
189
|
- lib/her/middleware/second_level_parse_json.rb
|
188
190
|
- lib/her/model.rb
|
@@ -205,8 +207,10 @@ files:
|
|
205
207
|
- lib/her/version.rb
|
206
208
|
- spec/api_spec.rb
|
207
209
|
- spec/collection_spec.rb
|
210
|
+
- spec/json_api/model_spec.rb
|
208
211
|
- spec/middleware/accept_json_spec.rb
|
209
212
|
- spec/middleware/first_level_parse_json_spec.rb
|
213
|
+
- spec/middleware/json_api_parser_spec.rb
|
210
214
|
- spec/middleware/second_level_parse_json_spec.rb
|
211
215
|
- spec/model/associations/association_proxy_spec.rb
|
212
216
|
- spec/model/associations_spec.rb
|
@@ -256,8 +260,10 @@ summary: A simple Representational State Transfer-based Hypertext Transfer Proto
|
|
256
260
|
test_files:
|
257
261
|
- spec/api_spec.rb
|
258
262
|
- spec/collection_spec.rb
|
263
|
+
- spec/json_api/model_spec.rb
|
259
264
|
- spec/middleware/accept_json_spec.rb
|
260
265
|
- spec/middleware/first_level_parse_json_spec.rb
|
266
|
+
- spec/middleware/json_api_parser_spec.rb
|
261
267
|
- spec/middleware/second_level_parse_json_spec.rb
|
262
268
|
- spec/model/associations/association_proxy_spec.rb
|
263
269
|
- spec/model/associations_spec.rb
|