restorm 1.0.0
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 +7 -0
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rubocop.yml +31 -0
- data/.rubocop_todo.yml +232 -0
- data/.ruby-version +1 -0
- data/.travis.yml +55 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +10 -0
- data/HER_README.md +1065 -0
- data/LICENSE +7 -0
- data/README.md +7 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-4.2 +6 -0
- data/gemfiles/Gemfile.activemodel-5.0 +6 -0
- data/gemfiles/Gemfile.activemodel-5.1 +6 -0
- data/gemfiles/Gemfile.activemodel-5.2 +6 -0
- data/gemfiles/Gemfile.faraday-1.0 +6 -0
- data/lib/restorm/api.rb +121 -0
- data/lib/restorm/collection.rb +13 -0
- data/lib/restorm/errors.rb +29 -0
- data/lib/restorm/json_api/model.rb +42 -0
- data/lib/restorm/middleware/accept_json.rb +18 -0
- data/lib/restorm/middleware/first_level_parse_json.rb +37 -0
- data/lib/restorm/middleware/json_api_parser.rb +37 -0
- data/lib/restorm/middleware/parse_json.rb +22 -0
- data/lib/restorm/middleware/second_level_parse_json.rb +37 -0
- data/lib/restorm/middleware.rb +12 -0
- data/lib/restorm/model/associations/association.rb +128 -0
- data/lib/restorm/model/associations/association_proxy.rb +44 -0
- data/lib/restorm/model/associations/belongs_to_association.rb +95 -0
- data/lib/restorm/model/associations/has_many_association.rb +100 -0
- data/lib/restorm/model/associations/has_one_association.rb +79 -0
- data/lib/restorm/model/associations.rb +141 -0
- data/lib/restorm/model/attributes.rb +322 -0
- data/lib/restorm/model/base.rb +33 -0
- data/lib/restorm/model/deprecated_methods.rb +61 -0
- data/lib/restorm/model/http.rb +119 -0
- data/lib/restorm/model/introspection.rb +67 -0
- data/lib/restorm/model/nested_attributes.rb +45 -0
- data/lib/restorm/model/orm.rb +299 -0
- data/lib/restorm/model/parse.rb +223 -0
- data/lib/restorm/model/paths.rb +125 -0
- data/lib/restorm/model/relation.rb +209 -0
- data/lib/restorm/model.rb +75 -0
- data/lib/restorm/version.rb +3 -0
- data/lib/restorm.rb +19 -0
- data/restorm.gemspec +29 -0
- data/spec/api_spec.rb +120 -0
- data/spec/collection_spec.rb +41 -0
- data/spec/json_api/model_spec.rb +169 -0
- data/spec/middleware/accept_json_spec.rb +11 -0
- data/spec/middleware/first_level_parse_json_spec.rb +63 -0
- data/spec/middleware/json_api_parser_spec.rb +52 -0
- data/spec/middleware/second_level_parse_json_spec.rb +35 -0
- data/spec/model/associations/association_proxy_spec.rb +29 -0
- data/spec/model/associations_spec.rb +911 -0
- data/spec/model/attributes_spec.rb +354 -0
- data/spec/model/callbacks_spec.rb +176 -0
- data/spec/model/dirty_spec.rb +133 -0
- data/spec/model/http_spec.rb +201 -0
- data/spec/model/introspection_spec.rb +81 -0
- data/spec/model/nested_attributes_spec.rb +135 -0
- data/spec/model/orm_spec.rb +704 -0
- data/spec/model/parse_spec.rb +520 -0
- data/spec/model/paths_spec.rb +348 -0
- data/spec/model/relation_spec.rb +247 -0
- data/spec/model/validations_spec.rb +43 -0
- data/spec/model_spec.rb +45 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/macros/her_macros.rb +17 -0
- data/spec/support/macros/model_macros.rb +36 -0
- data/spec/support/macros/request_macros.rb +27 -0
- metadata +203 -0
@@ -0,0 +1,209 @@
|
|
1
|
+
module Restorm
|
2
|
+
module Model
|
3
|
+
class Relation
|
4
|
+
|
5
|
+
# @private
|
6
|
+
attr_accessor :params
|
7
|
+
attr_writer :parent
|
8
|
+
|
9
|
+
# @private
|
10
|
+
def initialize(parent)
|
11
|
+
@parent = parent
|
12
|
+
@params = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# @private
|
16
|
+
def apply_to(attributes)
|
17
|
+
@params.merge(attributes)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Build a new resource
|
21
|
+
def build(attributes = {})
|
22
|
+
@parent.build(@params.merge(attributes))
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a query string parameter
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# @users = User.all
|
29
|
+
# # Fetched via GET "/users"
|
30
|
+
#
|
31
|
+
# @example
|
32
|
+
# @users = User.where(:approved => 1).all
|
33
|
+
# # Fetched via GET "/users?approved=1"
|
34
|
+
def where(params = {})
|
35
|
+
return self if params.blank? && !@_fetch.nil?
|
36
|
+
clone.tap do |r|
|
37
|
+
r.params = r.params.merge(params)
|
38
|
+
r.clear_fetch_cache!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
alias all where
|
42
|
+
|
43
|
+
# Bubble all methods to the fetched collection
|
44
|
+
#
|
45
|
+
# @private
|
46
|
+
def method_missing(method, *args, &blk)
|
47
|
+
fetch.send(method, *args, &blk)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @private
|
51
|
+
def respond_to?(method, *args)
|
52
|
+
super || fetch.respond_to?(method, *args)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @private
|
56
|
+
def nil?
|
57
|
+
fetch.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
# @private
|
61
|
+
def kind_of?(thing)
|
62
|
+
fetch.is_a?(thing)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Fetch a collection of resources
|
66
|
+
#
|
67
|
+
# @private
|
68
|
+
def fetch
|
69
|
+
@_fetch ||= begin
|
70
|
+
path = @parent.build_request_path(@parent.collection_path, @params)
|
71
|
+
method = @parent.method_for(:find)
|
72
|
+
@parent.request(@params.merge(:_method => method, :_path => path)) do |parsed_data, _|
|
73
|
+
@parent.new_collection(parsed_data)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Fetch specific resource(s) by their ID
|
79
|
+
#
|
80
|
+
# @example
|
81
|
+
# @user = User.find(1)
|
82
|
+
# # Fetched via GET "/users/1"
|
83
|
+
#
|
84
|
+
# @example
|
85
|
+
# @users = User.find([1, 2])
|
86
|
+
# # Fetched via GET "/users/1" and GET "/users/2"
|
87
|
+
def find(*ids)
|
88
|
+
params = @params.merge(ids.last.is_a?(Hash) ? ids.pop : {})
|
89
|
+
ids = Array(params[@parent.primary_key]) if params.key?(@parent.primary_key)
|
90
|
+
|
91
|
+
results = ids.flatten.compact.uniq.map do |id|
|
92
|
+
resource = nil
|
93
|
+
request_params = params.merge(
|
94
|
+
:_method => @parent.method_for(:find),
|
95
|
+
:_path => @parent.build_request_path(params.merge(@parent.primary_key => id))
|
96
|
+
)
|
97
|
+
|
98
|
+
@parent.request(request_params) do |parsed_data, response|
|
99
|
+
if response.success?
|
100
|
+
resource = @parent.new_from_parsed_data(parsed_data)
|
101
|
+
resource.run_callbacks :find
|
102
|
+
else
|
103
|
+
return nil
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
resource
|
108
|
+
end
|
109
|
+
|
110
|
+
ids.length > 1 || ids.first.is_a?(Array) ? results : results.first
|
111
|
+
end
|
112
|
+
|
113
|
+
# Fetch first resource with the given attributes.
|
114
|
+
#
|
115
|
+
# If no resource is found, returns <tt>nil</tt>.
|
116
|
+
#
|
117
|
+
# @example
|
118
|
+
# @user = User.find_by(name: "Tobias", age: 42)
|
119
|
+
# # Called via GET "/users?name=Tobias&age=42"
|
120
|
+
def find_by(params)
|
121
|
+
where(params).first
|
122
|
+
end
|
123
|
+
|
124
|
+
# Fetch first resource with the given attributes, or create a resource
|
125
|
+
# with the attributes if one is not found.
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# @user = User.find_or_create_by(email: "remi@example.com")
|
129
|
+
#
|
130
|
+
# # Returns the first item in the collection if present:
|
131
|
+
# # Called via GET "/users?email=remi@example.com"
|
132
|
+
#
|
133
|
+
# # If collection is empty:
|
134
|
+
# # POST /users with `email=remi@example.com`
|
135
|
+
# @user.email # => "remi@example.com"
|
136
|
+
# @user.new? # => false
|
137
|
+
def find_or_create_by(attributes)
|
138
|
+
find_by(attributes) || create(attributes)
|
139
|
+
end
|
140
|
+
|
141
|
+
# Fetch first resource with the given attributes, or initialize a resource
|
142
|
+
# with the attributes if one is not found.
|
143
|
+
#
|
144
|
+
# @example
|
145
|
+
# @user = User.find_or_initialize_by(email: "remi@example.com")
|
146
|
+
#
|
147
|
+
# # Returns the first item in the collection if present:
|
148
|
+
# # Called via GET "/users?email=remi@example.com"
|
149
|
+
#
|
150
|
+
# # If collection is empty:
|
151
|
+
# @user.email # => "remi@example.com"
|
152
|
+
# @user.new? # => true
|
153
|
+
def find_or_initialize_by(attributes)
|
154
|
+
find_by(attributes) || build(attributes)
|
155
|
+
end
|
156
|
+
|
157
|
+
# Create a resource and return it
|
158
|
+
#
|
159
|
+
# @example
|
160
|
+
# @user = User.create(:fullname => "Tobias Fünke")
|
161
|
+
# # Called via POST "/users/1" with `&fullname=Tobias+Fünke`
|
162
|
+
#
|
163
|
+
# @example
|
164
|
+
# @user = User.where(:email => "tobias@bluth.com").create(:fullname => "Tobias Fünke")
|
165
|
+
# # Called via POST "/users/1" with `&email=tobias@bluth.com&fullname=Tobias+Fünke`
|
166
|
+
def create(attributes = {})
|
167
|
+
attributes ||= {}
|
168
|
+
resource = @parent.new(@params.merge(attributes))
|
169
|
+
resource.save
|
170
|
+
|
171
|
+
resource
|
172
|
+
end
|
173
|
+
|
174
|
+
# Fetch a resource and create it if it's not found
|
175
|
+
#
|
176
|
+
# @example
|
177
|
+
# @user = User.where(:email => "remi@example.com").find_or_create
|
178
|
+
#
|
179
|
+
# # Returns the first item of the collection if present:
|
180
|
+
# # GET "/users?email=remi@example.com"
|
181
|
+
#
|
182
|
+
# # If collection is empty:
|
183
|
+
# # POST /users with `email=remi@example.com`
|
184
|
+
def first_or_create(attributes = {})
|
185
|
+
fetch.first || create(attributes)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Fetch a resource and build it if it's not found
|
189
|
+
#
|
190
|
+
# @example
|
191
|
+
# @user = User.where(:email => "remi@example.com").find_or_initialize
|
192
|
+
#
|
193
|
+
# # Returns the first item of the collection if present:
|
194
|
+
# # GET "/users?email=remi@example.com"
|
195
|
+
#
|
196
|
+
# # If collection is empty:
|
197
|
+
# @user.email # => "remi@example.com"
|
198
|
+
# @user.new? # => true
|
199
|
+
def first_or_initialize(attributes = {})
|
200
|
+
fetch.first || build(attributes)
|
201
|
+
end
|
202
|
+
|
203
|
+
# @private
|
204
|
+
def clear_fetch_cache!
|
205
|
+
instance_variable_set(:@_fetch, nil)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "restorm/model/base"
|
2
|
+
require "restorm/model/deprecated_methods"
|
3
|
+
require "restorm/model/http"
|
4
|
+
require "restorm/model/attributes"
|
5
|
+
require "restorm/model/relation"
|
6
|
+
require "restorm/model/orm"
|
7
|
+
require "restorm/model/parse"
|
8
|
+
require "restorm/model/associations"
|
9
|
+
require "restorm/model/introspection"
|
10
|
+
require "restorm/model/paths"
|
11
|
+
require "restorm/model/nested_attributes"
|
12
|
+
require "active_model"
|
13
|
+
|
14
|
+
module Restorm
|
15
|
+
# This module is the main element of Restorm. After creating a Restorm::API object,
|
16
|
+
# include this module in your models to get a few magic methods defined in them.
|
17
|
+
#
|
18
|
+
# @example
|
19
|
+
# class User
|
20
|
+
# include Restorm::Model
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @user = User.new(:name => "Rémi")
|
24
|
+
# @user.save
|
25
|
+
module Model
|
26
|
+
extend ActiveSupport::Concern
|
27
|
+
|
28
|
+
# Restorm modules
|
29
|
+
include Restorm::Model::Base
|
30
|
+
include Restorm::Model::DeprecatedMethods
|
31
|
+
include Restorm::Model::Attributes
|
32
|
+
include Restorm::Model::ORM
|
33
|
+
include Restorm::Model::HTTP
|
34
|
+
include Restorm::Model::Parse
|
35
|
+
include Restorm::Model::Introspection
|
36
|
+
include Restorm::Model::Paths
|
37
|
+
include Restorm::Model::Associations
|
38
|
+
include Restorm::Model::NestedAttributes
|
39
|
+
|
40
|
+
# Supported ActiveModel modules
|
41
|
+
include ActiveModel::AttributeMethods
|
42
|
+
include ActiveModel::Validations
|
43
|
+
include ActiveModel::Validations::Callbacks
|
44
|
+
include ActiveModel::Conversion
|
45
|
+
include ActiveModel::Dirty
|
46
|
+
|
47
|
+
# Class methods
|
48
|
+
included do
|
49
|
+
# Assign the default API
|
50
|
+
use_api Restorm::API.default_api
|
51
|
+
method_for :create, :post
|
52
|
+
method_for :update, :put
|
53
|
+
method_for :find, :get
|
54
|
+
method_for :destroy, :delete
|
55
|
+
method_for :new, :get
|
56
|
+
|
57
|
+
# Define the default primary key
|
58
|
+
primary_key :id
|
59
|
+
|
60
|
+
# Define default storage accessors for errors and metadata
|
61
|
+
store_response_errors :response_errors
|
62
|
+
store_metadata :metadata
|
63
|
+
|
64
|
+
# Include ActiveModel naming methods
|
65
|
+
extend ActiveModel::Translation
|
66
|
+
|
67
|
+
# Configure ActiveModel callbacks
|
68
|
+
extend ActiveModel::Callbacks
|
69
|
+
define_model_callbacks :create, :update, :save, :find, :destroy, :initialize
|
70
|
+
|
71
|
+
# Define matchers for attr? and attr= methods
|
72
|
+
define_attribute_method_matchers
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/restorm.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require "restorm/version"
|
2
|
+
|
3
|
+
require "multi_json"
|
4
|
+
require "faraday"
|
5
|
+
require "active_support"
|
6
|
+
require "active_support/inflector"
|
7
|
+
require "active_support/core_ext/hash"
|
8
|
+
|
9
|
+
require "restorm/model"
|
10
|
+
require "restorm/api"
|
11
|
+
require "restorm/middleware"
|
12
|
+
require "restorm/errors"
|
13
|
+
require "restorm/collection"
|
14
|
+
|
15
|
+
module Restorm
|
16
|
+
module JsonApi
|
17
|
+
autoload :Model, 'restorm/json_api/model'
|
18
|
+
end
|
19
|
+
end
|
data/restorm.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
$:.push File.expand_path("../lib", __FILE__)
|
4
|
+
require "restorm/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "restorm"
|
8
|
+
s.version = Restorm::VERSION
|
9
|
+
s.authors = ["Rémi Prévost", "Kates Gasis"]
|
10
|
+
s.email = ["katesgasis@gmail.com"]
|
11
|
+
s.homepage = "https://github.com/kates/restorm"
|
12
|
+
s.license = "MIT"
|
13
|
+
s.summary = "A simple Representational State Transfer-based Hypertext Transfer Protocol-powered Object Relational Mapper."
|
14
|
+
s.description = "Restorm is an ORM that maps REST resources and collections to Ruby objects"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "json", "~> 2.6.3"
|
22
|
+
s.add_development_dependency "rake", "~> 13.0.6"
|
23
|
+
s.add_development_dependency "rspec", "~> 3.12"
|
24
|
+
|
25
|
+
s.add_runtime_dependency "activemodel", ">= 4.2.1"
|
26
|
+
s.add_runtime_dependency "faraday", "~> 2.7.4"
|
27
|
+
|
28
|
+
s.add_runtime_dependency "multi_json", "~> 1.15.0"
|
29
|
+
end
|
data/spec/api_spec.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), "spec_helper.rb")
|
4
|
+
|
5
|
+
describe Restorm::API do
|
6
|
+
subject { Restorm::API.new }
|
7
|
+
|
8
|
+
context "initialization" do
|
9
|
+
describe "#setup" do
|
10
|
+
context "when setting custom middleware" do
|
11
|
+
before do
|
12
|
+
class Foo; end
|
13
|
+
class Bar; end
|
14
|
+
|
15
|
+
subject.setup url: "https://api.example.com" do |connection|
|
16
|
+
connection.use Foo
|
17
|
+
connection.use Bar
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
specify { expect(subject.connection.builder.handlers).to eq([Foo, Bar]) }
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when setting custom options" do
|
25
|
+
before { subject.setup foo: { bar: "baz" }, url: "https://api.example.com" }
|
26
|
+
|
27
|
+
describe "#options" do
|
28
|
+
it { expect(subject.options).to eq(foo: { bar: "baz" }, url: "https://api.example.com") }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "#request" do
|
34
|
+
before do
|
35
|
+
class SimpleParser < Faraday::Middleware
|
36
|
+
|
37
|
+
def on_complete(env)
|
38
|
+
env[:body] = { data: env[:body] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "making HTTP requests" do
|
44
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "/foo")[:parsed_data] }
|
45
|
+
before do
|
46
|
+
subject.setup url: "https://api.example.com" do |builder|
|
47
|
+
builder.use SimpleParser
|
48
|
+
builder.adapter(:test) { |stub| stub.get("/foo") { [200, {}, "Foo, it is."] } }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
specify { expect(parsed_data[:data]).to eq("Foo, it is.") }
|
53
|
+
end
|
54
|
+
|
55
|
+
context "making HTTP requests while specifying custom HTTP headers" do
|
56
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "/foo", _headers: { "X-Page" => 2 })[:parsed_data] }
|
57
|
+
|
58
|
+
before do
|
59
|
+
subject.setup url: "https://api.example.com" do |builder|
|
60
|
+
builder.use SimpleParser
|
61
|
+
builder.adapter(:test) { |stub| stub.get("/foo") { |env| [200, {}, "Foo, it is page #{env[:request_headers]['X-Page']}."] } }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
specify { expect(parsed_data[:data]).to eq("Foo, it is page 2.") }
|
66
|
+
end
|
67
|
+
|
68
|
+
context "parsing a request with the default parser" do
|
69
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "users/1")[:parsed_data] }
|
70
|
+
before do
|
71
|
+
subject.setup url: "https://api.example.com" do |builder|
|
72
|
+
builder.use Restorm::Middleware::FirstLevelParseJSON
|
73
|
+
builder.adapter :test do |stub|
|
74
|
+
stub.get("/users/1") { [200, {}, MultiJson.dump(id: 1, name: "George Michael Bluth", errors: ["This is a single error"], metadata: { page: 1, per_page: 10 })] }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
specify do
|
80
|
+
expect(parsed_data[:data]).to eq(id: 1, name: "George Michael Bluth")
|
81
|
+
expect(parsed_data[:errors]).to eq(["This is a single error"])
|
82
|
+
expect(parsed_data[:metadata]).to eq(page: 1, per_page: 10)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "parsing a request with a custom parser" do
|
87
|
+
let(:parsed_data) { subject.request(_method: :get, _path: "users/1")[:parsed_data] }
|
88
|
+
before do
|
89
|
+
class CustomParser < Faraday::Middleware
|
90
|
+
|
91
|
+
def on_complete(env)
|
92
|
+
json = MultiJson.load(env[:body], symbolize_keys: true)
|
93
|
+
errors = json.delete(:errors) || []
|
94
|
+
metadata = json.delete(:metadata) || {}
|
95
|
+
env[:body] = {
|
96
|
+
data: json,
|
97
|
+
errors: errors,
|
98
|
+
metadata: metadata
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
subject.setup url: "https://api.example.com" do |builder|
|
104
|
+
builder.use CustomParser
|
105
|
+
builder.use Faraday::Request::UrlEncoded
|
106
|
+
builder.adapter :test do |stub|
|
107
|
+
stub.get("/users/1") { [200, {}, MultiJson.dump(id: 1, name: "George Michael Bluth")] }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
specify do
|
113
|
+
expect(parsed_data[:data]).to eq(id: 1, name: "George Michael Bluth")
|
114
|
+
expect(parsed_data[:errors]).to eq([])
|
115
|
+
expect(parsed_data[:metadata]).to eq({})
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Restorm::Collection do
|
4
|
+
let(:items) { [1, 2, 3, 4] }
|
5
|
+
let(:metadata) { { name: "Testname" } }
|
6
|
+
let(:errors) { { name: ["not_present"] } }
|
7
|
+
|
8
|
+
describe "#new" do
|
9
|
+
context "without parameters" do
|
10
|
+
subject { Restorm::Collection.new }
|
11
|
+
|
12
|
+
it { is_expected.to eq([]) }
|
13
|
+
|
14
|
+
describe "#metadata" do
|
15
|
+
subject { super().metadata }
|
16
|
+
it { is_expected.to eq({}) }
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "#errors" do
|
20
|
+
subject { super().errors }
|
21
|
+
it { is_expected.to eq({}) }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with parameters" do
|
26
|
+
subject { Restorm::Collection.new(items, metadata, errors) }
|
27
|
+
|
28
|
+
it { is_expected.to eq([1, 2, 3, 4]) }
|
29
|
+
|
30
|
+
describe "#metadata" do
|
31
|
+
subject { super().metadata }
|
32
|
+
it { is_expected.to eq(name: "Testname") }
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "#errors" do
|
36
|
+
subject { super().errors }
|
37
|
+
it { is_expected.to eq(name: ["not_present"]) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Restorm::JsonApi::Model do
|
4
|
+
before do
|
5
|
+
Restorm::API.setup url: "https://api.example.com" do |connection|
|
6
|
+
connection.use Restorm::Middleware::JsonApiParser
|
7
|
+
connection.adapter :test do |stub|
|
8
|
+
stub.get("/users/1") do
|
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
|
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
|
+
{
|
52
|
+
type: "users",
|
53
|
+
attributes: {
|
54
|
+
name: "Jeremy Lin"
|
55
|
+
}
|
56
|
+
}) do
|
57
|
+
[
|
58
|
+
201,
|
59
|
+
{},
|
60
|
+
{
|
61
|
+
data: {
|
62
|
+
id: 3,
|
63
|
+
type: "users",
|
64
|
+
attributes: {
|
65
|
+
name: "Jeremy Lin"
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
}.to_json
|
70
|
+
]
|
71
|
+
end
|
72
|
+
|
73
|
+
stub.patch("/users/1", data:
|
74
|
+
{
|
75
|
+
type: "users",
|
76
|
+
id: 1,
|
77
|
+
attributes: {
|
78
|
+
name: "Fed GOAT"
|
79
|
+
}
|
80
|
+
}) do
|
81
|
+
[
|
82
|
+
200,
|
83
|
+
{},
|
84
|
+
{
|
85
|
+
data: {
|
86
|
+
id: 1,
|
87
|
+
type: "users",
|
88
|
+
attributes: {
|
89
|
+
name: "Fed GOAT"
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
}.to_json
|
94
|
+
]
|
95
|
+
end
|
96
|
+
|
97
|
+
stub.delete("/users/1") do
|
98
|
+
[204, {}, {}]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
spawn_model("Foo::User", type: Restorm::JsonApi::Model)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "allows configuration of type" do
|
107
|
+
spawn_model("Foo::Bar", type: Restorm::JsonApi::Model) do
|
108
|
+
type :foobars
|
109
|
+
end
|
110
|
+
|
111
|
+
expect(Foo::Bar.instance_variable_get("@type")).to eql("foobars")
|
112
|
+
end
|
113
|
+
|
114
|
+
it "finds models by id" do
|
115
|
+
user = Foo::User.find(1)
|
116
|
+
expect(user.attributes).to eql(
|
117
|
+
"id" => 1,
|
118
|
+
"name" => "Roger Federer"
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "finds a collection of models" do
|
123
|
+
users = Foo::User.all
|
124
|
+
expect(users.map(&:attributes)).to match_array(
|
125
|
+
[
|
126
|
+
{
|
127
|
+
"id" => 1,
|
128
|
+
"name" => "Roger Federer"
|
129
|
+
},
|
130
|
+
{
|
131
|
+
"id" => 2,
|
132
|
+
"name" => "Kei Nishikori"
|
133
|
+
}
|
134
|
+
]
|
135
|
+
)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "creates a Foo::User" do
|
139
|
+
user = Foo::User.new(name: "Jeremy Lin")
|
140
|
+
user.save
|
141
|
+
expect(user.attributes).to eql(
|
142
|
+
"id" => 3,
|
143
|
+
"name" => "Jeremy Lin"
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
it "updates a Foo::User" do
|
148
|
+
user = Foo::User.find(1)
|
149
|
+
user.name = "Fed GOAT"
|
150
|
+
user.save
|
151
|
+
expect(user.attributes).to eql(
|
152
|
+
"id" => 1,
|
153
|
+
"name" => "Fed GOAT"
|
154
|
+
)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "destroys a Foo::User" do
|
158
|
+
user = Foo::User.find(1)
|
159
|
+
expect(user.destroy).to be_destroyed
|
160
|
+
end
|
161
|
+
|
162
|
+
context "undefined methods" do
|
163
|
+
it "removes methods that are not compatible with json api" do
|
164
|
+
[:parse_root_in_json, :include_root_in_json, :root_element, :primary_key].each do |method|
|
165
|
+
expect { Foo::User.new.send(method, :foo) }.to raise_error NoMethodError, "Restorm::JsonApi::Model does not support the #{method} configuration option"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|