him 0.1.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/.github/workflows/ci.yml +40 -0
- data/.gitignore +6 -0
- data/.qlty/qlty.toml +57 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +2 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +2 -0
- data/LICENSE +8 -0
- data/README.md +1007 -0
- data/Rakefile +11 -0
- data/UPGRADE.md +101 -0
- data/gemfiles/Gemfile.activemodel-6.1 +6 -0
- data/gemfiles/Gemfile.activemodel-7.0 +6 -0
- data/gemfiles/Gemfile.activemodel-7.1 +6 -0
- data/gemfiles/Gemfile.activemodel-7.2 +6 -0
- data/gemfiles/Gemfile.activemodel-8.0 +6 -0
- data/him.gemspec +28 -0
- data/lib/him/api.rb +121 -0
- data/lib/him/collection.rb +21 -0
- data/lib/him/errors.rb +29 -0
- data/lib/him/json_api/model.rb +42 -0
- data/lib/him/middleware/accept_json.rb +18 -0
- data/lib/him/middleware/first_level_parse_json.rb +37 -0
- data/lib/him/middleware/json_api_parser.rb +65 -0
- data/lib/him/middleware/parse_json.rb +22 -0
- data/lib/him/middleware/second_level_parse_json.rb +37 -0
- data/lib/him/middleware.rb +12 -0
- data/lib/him/model/associations/association.rb +147 -0
- data/lib/him/model/associations/association_proxy.rb +47 -0
- data/lib/him/model/associations/belongs_to_association.rb +95 -0
- data/lib/him/model/associations/has_many_association.rb +113 -0
- data/lib/him/model/associations/has_one_association.rb +79 -0
- data/lib/him/model/associations.rb +141 -0
- data/lib/him/model/attributes.rb +337 -0
- data/lib/him/model/base.rb +33 -0
- data/lib/him/model/http.rb +113 -0
- data/lib/him/model/introspection.rb +77 -0
- data/lib/him/model/nested_attributes.rb +45 -0
- data/lib/him/model/orm.rb +306 -0
- data/lib/him/model/parse.rb +224 -0
- data/lib/him/model/paths.rb +125 -0
- data/lib/him/model/relation.rb +212 -0
- data/lib/him/model.rb +79 -0
- data/lib/him/version.rb +3 -0
- data/lib/him.rb +22 -0
- data/spec/api_spec.rb +120 -0
- data/spec/collection_spec.rb +70 -0
- data/spec/json_api/model_spec.rb +260 -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 +1010 -0
- data/spec/model/attributes_spec.rb +384 -0
- data/spec/model/callbacks_spec.rb +194 -0
- data/spec/model/dirty_spec.rb +133 -0
- data/spec/model/http_spec.rb +187 -0
- data/spec/model/introspection_spec.rb +110 -0
- data/spec/model/nested_attributes_spec.rb +135 -0
- data/spec/model/orm_spec.rb +717 -0
- data/spec/model/parse_spec.rb +619 -0
- data/spec/model/paths_spec.rb +348 -0
- data/spec/model/relation_spec.rb +255 -0
- data/spec/model/validations_spec.rb +45 -0
- data/spec/model_spec.rb +55 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/support/extensions/array.rb +6 -0
- data/spec/support/extensions/hash.rb +6 -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 +201 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
module Him
|
|
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
|
+
if @params.values.include?([])
|
|
71
|
+
Him::Collection.new
|
|
72
|
+
else
|
|
73
|
+
path = @parent.build_request_path(@parent.collection_path, @params)
|
|
74
|
+
method = @parent.method_for(:find)
|
|
75
|
+
@parent.request(@params.merge(:_method => method, :_path => path)) do |parsed_data, _|
|
|
76
|
+
@parent.new_collection(parsed_data)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Fetch specific resource(s) by their ID
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# @user = User.find(1)
|
|
86
|
+
# # Fetched via GET "/users/1"
|
|
87
|
+
#
|
|
88
|
+
# @example
|
|
89
|
+
# @users = User.find([1, 2])
|
|
90
|
+
# # Fetched via GET "/users/1" and GET "/users/2"
|
|
91
|
+
def find(*ids)
|
|
92
|
+
params = @params.merge(ids.last.is_a?(Hash) ? ids.pop : {})
|
|
93
|
+
ids = Array(params[@parent.primary_key]) if params.key?(@parent.primary_key)
|
|
94
|
+
|
|
95
|
+
results = ids.flatten.compact.uniq.map do |id|
|
|
96
|
+
resource = nil
|
|
97
|
+
request_params = params.merge(
|
|
98
|
+
:_method => @parent.method_for(:find),
|
|
99
|
+
:_path => @parent.build_request_path(params.merge(@parent.primary_key => id))
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
@parent.request(request_params) do |parsed_data, response|
|
|
103
|
+
if response.success?
|
|
104
|
+
resource = @parent.new_from_parsed_data(parsed_data)
|
|
105
|
+
else
|
|
106
|
+
return nil
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
resource
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
ids.length > 1 || ids.first.is_a?(Array) ? results : results.first
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Fetch first resource with the given attributes.
|
|
117
|
+
#
|
|
118
|
+
# If no resource is found, returns <tt>nil</tt>.
|
|
119
|
+
#
|
|
120
|
+
# @example
|
|
121
|
+
# @user = User.find_by(name: "Tobias", age: 42)
|
|
122
|
+
# # Called via GET "/users?name=Tobias&age=42"
|
|
123
|
+
def find_by(params)
|
|
124
|
+
where(params).first
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Fetch first resource with the given attributes, or create a resource
|
|
128
|
+
# with the attributes if one is not found.
|
|
129
|
+
#
|
|
130
|
+
# @example
|
|
131
|
+
# @user = User.find_or_create_by(email: "remi@example.com")
|
|
132
|
+
#
|
|
133
|
+
# # Returns the first item in the collection if present:
|
|
134
|
+
# # Called via GET "/users?email=remi@example.com"
|
|
135
|
+
#
|
|
136
|
+
# # If collection is empty:
|
|
137
|
+
# # POST /users with `email=remi@example.com`
|
|
138
|
+
# @user.email # => "remi@example.com"
|
|
139
|
+
# @user.new? # => false
|
|
140
|
+
def find_or_create_by(attributes)
|
|
141
|
+
find_by(attributes) || create(attributes)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Fetch first resource with the given attributes, or initialize a resource
|
|
145
|
+
# with the attributes if one is not found.
|
|
146
|
+
#
|
|
147
|
+
# @example
|
|
148
|
+
# @user = User.find_or_initialize_by(email: "remi@example.com")
|
|
149
|
+
#
|
|
150
|
+
# # Returns the first item in the collection if present:
|
|
151
|
+
# # Called via GET "/users?email=remi@example.com"
|
|
152
|
+
#
|
|
153
|
+
# # If collection is empty:
|
|
154
|
+
# @user.email # => "remi@example.com"
|
|
155
|
+
# @user.new? # => true
|
|
156
|
+
def find_or_initialize_by(attributes)
|
|
157
|
+
find_by(attributes) || build(attributes)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Create a resource and return it
|
|
161
|
+
#
|
|
162
|
+
# @example
|
|
163
|
+
# @user = User.create(:fullname => "Tobias Fünke")
|
|
164
|
+
# # Called via POST "/users/1" with `&fullname=Tobias+Fünke`
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# @user = User.where(:email => "tobias@bluth.com").create(:fullname => "Tobias Fünke")
|
|
168
|
+
# # Called via POST "/users/1" with `&email=tobias@bluth.com&fullname=Tobias+Fünke`
|
|
169
|
+
def create(attributes = {})
|
|
170
|
+
attributes ||= {}
|
|
171
|
+
resource = @parent.new(@params.merge(attributes))
|
|
172
|
+
resource.save
|
|
173
|
+
|
|
174
|
+
resource
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Fetch a resource and create it if it's not found
|
|
178
|
+
#
|
|
179
|
+
# @example
|
|
180
|
+
# @user = User.where(:email => "remi@example.com").find_or_create
|
|
181
|
+
#
|
|
182
|
+
# # Returns the first item of the collection if present:
|
|
183
|
+
# # GET "/users?email=remi@example.com"
|
|
184
|
+
#
|
|
185
|
+
# # If collection is empty:
|
|
186
|
+
# # POST /users with `email=remi@example.com`
|
|
187
|
+
def first_or_create(attributes = {})
|
|
188
|
+
fetch.first || create(attributes)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Fetch a resource and build it if it's not found
|
|
192
|
+
#
|
|
193
|
+
# @example
|
|
194
|
+
# @user = User.where(:email => "remi@example.com").find_or_initialize
|
|
195
|
+
#
|
|
196
|
+
# # Returns the first item of the collection if present:
|
|
197
|
+
# # GET "/users?email=remi@example.com"
|
|
198
|
+
#
|
|
199
|
+
# # If collection is empty:
|
|
200
|
+
# @user.email # => "remi@example.com"
|
|
201
|
+
# @user.new? # => true
|
|
202
|
+
def first_or_initialize(attributes = {})
|
|
203
|
+
fetch.first || build(attributes)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# @private
|
|
207
|
+
def clear_fetch_cache!
|
|
208
|
+
instance_variable_set(:@_fetch, nil)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
data/lib/him/model.rb
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require "him/model/base"
|
|
2
|
+
require "him/model/http"
|
|
3
|
+
require "him/model/attributes"
|
|
4
|
+
require "him/model/relation"
|
|
5
|
+
require "him/model/orm"
|
|
6
|
+
require "him/model/parse"
|
|
7
|
+
require "him/model/associations"
|
|
8
|
+
require "him/model/introspection"
|
|
9
|
+
require "him/model/paths"
|
|
10
|
+
require "him/model/nested_attributes"
|
|
11
|
+
require "active_model"
|
|
12
|
+
|
|
13
|
+
module Him
|
|
14
|
+
# This module is the main element of Her. After creating a Him::API object,
|
|
15
|
+
# include this module in your models to get a few magic methods defined in them.
|
|
16
|
+
#
|
|
17
|
+
# @example
|
|
18
|
+
# class User
|
|
19
|
+
# include Him::Model
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# @user = User.new(:name => "Rémi")
|
|
23
|
+
# @user.save
|
|
24
|
+
module Model
|
|
25
|
+
extend ActiveSupport::Concern
|
|
26
|
+
|
|
27
|
+
# Her modules
|
|
28
|
+
include Him::Model::Base
|
|
29
|
+
include Him::Model::Attributes
|
|
30
|
+
include Him::Model::ORM
|
|
31
|
+
include Him::Model::HTTP
|
|
32
|
+
include Him::Model::Parse
|
|
33
|
+
include Him::Model::Introspection
|
|
34
|
+
include Him::Model::Paths
|
|
35
|
+
include Him::Model::Associations
|
|
36
|
+
include Him::Model::NestedAttributes
|
|
37
|
+
|
|
38
|
+
# Supported ActiveModel modules
|
|
39
|
+
include ActiveModel::AttributeMethods
|
|
40
|
+
include ActiveModel::Validations
|
|
41
|
+
include ActiveModel::Validations::Callbacks
|
|
42
|
+
include ActiveModel::Conversion
|
|
43
|
+
include ActiveModel::Dirty
|
|
44
|
+
|
|
45
|
+
# Override ActiveModel::Dirty's attribute_changed_in_place? to use
|
|
46
|
+
# Her's change tracking. This allows validates_numericality_of to work.
|
|
47
|
+
def attribute_changed_in_place?(attribute_name)
|
|
48
|
+
!changes[attribute_name.to_s].nil?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Class methods
|
|
52
|
+
included do
|
|
53
|
+
# Assign the default API
|
|
54
|
+
use_api Him::API.default_api
|
|
55
|
+
method_for :create, :post
|
|
56
|
+
method_for :update, :put
|
|
57
|
+
method_for :find, :get
|
|
58
|
+
method_for :destroy, :delete
|
|
59
|
+
method_for :new, :get
|
|
60
|
+
|
|
61
|
+
# Define the default primary key
|
|
62
|
+
primary_key :id
|
|
63
|
+
|
|
64
|
+
# Define default storage accessors for errors and metadata
|
|
65
|
+
store_response_errors :response_errors
|
|
66
|
+
store_metadata :metadata
|
|
67
|
+
|
|
68
|
+
# Include ActiveModel naming methods
|
|
69
|
+
extend ActiveModel::Translation
|
|
70
|
+
|
|
71
|
+
# Configure ActiveModel callbacks
|
|
72
|
+
extend ActiveModel::Callbacks
|
|
73
|
+
define_model_callbacks :create, :update, :save, :find, :destroy, :initialize
|
|
74
|
+
|
|
75
|
+
# Define matchers for attr? and attr= methods
|
|
76
|
+
define_attribute_method_matchers
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/him/version.rb
ADDED
data/lib/him.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
require "him/version"
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "faraday"
|
|
5
|
+
require "active_support"
|
|
6
|
+
require "active_support/inflector"
|
|
7
|
+
require "active_support/core_ext/hash"
|
|
8
|
+
|
|
9
|
+
require "him/model"
|
|
10
|
+
require "him/api"
|
|
11
|
+
require "him/middleware"
|
|
12
|
+
require "him/errors"
|
|
13
|
+
require "him/collection"
|
|
14
|
+
|
|
15
|
+
module Him
|
|
16
|
+
module JsonApi
|
|
17
|
+
autoload :Model, "him/json_api/model"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Backward compatibility alias for migration from Her
|
|
22
|
+
Her = Him
|
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 Him::API do
|
|
6
|
+
subject { Him::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 Him::Middleware::FirstLevelParseJSON
|
|
73
|
+
builder.adapter :test do |stub|
|
|
74
|
+
stub.get("/users/1") { [200, {}, JSON.generate(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 = JSON.parse(env[:body], symbolize_names: 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, {}, JSON.generate(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,70 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
describe Him::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 { Him::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 { Him::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
|
+
|
|
42
|
+
describe "Array methods preserve Collection type" do
|
|
43
|
+
subject { Him::Collection.new(items, metadata, errors) }
|
|
44
|
+
|
|
45
|
+
it "returns a Collection from #select" do
|
|
46
|
+
result = subject.select(&:odd?)
|
|
47
|
+
expect(result).to be_a(Him::Collection)
|
|
48
|
+
expect(result).to eq([1, 3])
|
|
49
|
+
expect(result.metadata).to eq(name: "Testname")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "returns a Collection from #reject" do
|
|
53
|
+
result = subject.reject(&:odd?)
|
|
54
|
+
expect(result).to be_a(Him::Collection)
|
|
55
|
+
expect(result).to eq([2, 4])
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it "returns a Collection from #map" do
|
|
59
|
+
result = subject.map { |x| x * 2 }
|
|
60
|
+
expect(result).to be_a(Him::Collection)
|
|
61
|
+
expect(result).to eq([2, 4, 6, 8])
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
it "returns a Collection from #first with count" do
|
|
65
|
+
result = subject.first(2)
|
|
66
|
+
expect(result).to be_a(Him::Collection)
|
|
67
|
+
expect(result).to eq([1, 2])
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|