zuora-ruby 0.2.0 → 0.3.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 +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +11 -1
- data/README.md +101 -172
- data/bin/console +2 -2
- data/lib/utils/schema_model.rb +194 -0
- data/lib/zuora.rb +30 -72
- data/lib/zuora/calls/amend.rb +68 -0
- data/lib/zuora/calls/create.rb +9 -0
- data/lib/zuora/calls/delete.rb +20 -0
- data/lib/zuora/calls/generate.rb +22 -0
- data/lib/zuora/calls/login.rb +21 -0
- data/lib/zuora/calls/query.rb +15 -0
- data/lib/zuora/calls/subscribe.rb +75 -0
- data/lib/zuora/calls/update.rb +9 -0
- data/lib/zuora/calls/upsert.rb +29 -0
- data/lib/zuora/client.rb +84 -94
- data/lib/zuora/dispatcher.rb +45 -0
- data/lib/zuora/object.rb +5 -0
- data/lib/zuora/response.rb +50 -0
- data/lib/zuora/utils/envelope.rb +98 -0
- data/lib/zuora/version.rb +1 -4
- data/zuora_ruby.gemspec +10 -11
- metadata +57 -67
- data/lib/zuora/models.rb +0 -11
- data/lib/zuora/models/account.rb +0 -64
- data/lib/zuora/models/card_holder.rb +0 -54
- data/lib/zuora/models/contact.rb +0 -71
- data/lib/zuora/models/dirty.rb +0 -192
- data/lib/zuora/models/payment_method.rb +0 -1
- data/lib/zuora/models/payment_methods/credit_card.rb +0 -37
- data/lib/zuora/models/rate_plan.rb +0 -17
- data/lib/zuora/models/rate_plan_charge.rb +0 -119
- data/lib/zuora/models/subscription.rb +0 -80
- data/lib/zuora/models/tier.rb +0 -27
- data/lib/zuora/models/validation_predicates.rb +0 -29
- data/lib/zuora/resources.rb +0 -6
- data/lib/zuora/resources/accounts.rb +0 -20
- data/lib/zuora/resources/payment_methods.rb +0 -1
- data/lib/zuora/resources/payment_methods/credit_card.rb +0 -24
- data/lib/zuora/resources/subscriptions.rb +0 -17
- data/lib/zuora/serializers.rb +0 -1
- data/lib/zuora/serializers/attribute.rb +0 -35
- data/lib/zuora/serializers/noop.rb +0 -18
- data/zuora/fixtures/vcr_cassettes/account_create_.yml +0 -111
- data/zuora/fixtures/vcr_cassettes/account_update_.yml +0 -113
- data/zuora/fixtures/vcr_cassettes/subscription_create_.yml +0 -114
- data/zuora/fixtures/vcr_cassettes/subscription_update_.yml +0 -114
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59ac3797046acdb3f30dbd498d3f5368024d6c8f
|
4
|
+
data.tar.gz: c747cfe5f6c0e2dfeeb46da7a0279761e201ecba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2e9865623c907ad008b942f39fb831979f6fe65f8b7d159f4176ae074ea41867e38883d77e891922e1e3e70da7685a3ad617dbe9cbf6aedfefceba8513f2b56
|
7
|
+
data.tar.gz: ded935331529eecc8ce365652be19e7fadb252c949e919d7e0bca07af74211d7f1cf5cc4264c3dd2d721bfdefcdb1893007ba3b91e25e678298047a70d1797a7
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -3,6 +3,7 @@ AllCops:
|
|
3
3
|
- 'db/**/*'
|
4
4
|
- 'config/**/*'
|
5
5
|
- 'vendor/**/*'
|
6
|
+
- 'scratchpad.rb'
|
6
7
|
|
7
8
|
Style/Encoding:
|
8
9
|
Enabled: true
|
@@ -43,4 +44,13 @@ Style/MultilineOperationIndentation:
|
|
43
44
|
EnforcedStyle: indented
|
44
45
|
SupportedStyles:
|
45
46
|
- aligned
|
46
|
-
- indented
|
47
|
+
- indented
|
48
|
+
|
49
|
+
Style/ClassAndModuleChildren:
|
50
|
+
Enabled: false
|
51
|
+
|
52
|
+
Metrics/ClassLength:
|
53
|
+
Enabled: false
|
54
|
+
|
55
|
+
Style/EachWithObject:
|
56
|
+
Enabled: false
|
data/README.md
CHANGED
@@ -2,195 +2,115 @@
|
|
2
2
|
[](https://codeclimate.com/repos/569444dfa3d810003a00313f/feed)
|
3
3
|
[](https://codeclimate.com/repos/569444dfa3d810003a00313f/coverage)
|
4
4
|
|
5
|
-
# Zuora
|
5
|
+
# Zuora SOAP API Client
|
6
6
|
|
7
|
-
|
7
|
+
## Features
|
8
|
+
* HTTP client to Zuora SOAP API
|
9
|
+
* Authentication and session storage
|
10
|
+
* SOAP XML request constructors from Ruby data
|
11
|
+
* Support for custom Zuora fields
|
12
|
+
* Light validation of SOAP call parameters
|
13
|
+
* Light wrapper over response, providing a Ruby object interface over Zuora's returned XML response
|
8
14
|
|
9
|
-
|
10
|
-
A base module called `DirtyValidAttr` provides `dirty_model_attr`
|
11
|
-
* **Accessors**: attribute name provides getters and setters, as in `attr_accessor`
|
12
|
-
* **Validations** `valid: max_length(3) `
|
13
|
-
* Includes a library of predicate higher order validation functions
|
14
|
-
* **Coercions** `coerce: ->(value) { value.to_s } `
|
15
|
-
* **Type Checks** `type: String`
|
16
|
-
* **Required Attributes**: `:required: true`
|
15
|
+
## Usage
|
17
16
|
|
18
|
-
### Resource
|
19
|
-
* **HTTP requests**: that are authenticated with provided credentials
|
20
|
-
* **Zuora API endpoints**:
|
21
17
|
|
22
|
-
###
|
23
|
-
- **Serialization**: Ruby <=> JSON serializer provided, just provide a module or class that respnds to `.serialize(hash)`
|
18
|
+
### Client
|
24
19
|
|
25
|
-
|
26
|
-
- **Factories**: for generating sample valid and invalid data that works with the API
|
27
|
-
- **Unit**: factories for testing model valdiations
|
28
|
-
- **Integration**: Tests against or memoized (via `VCR`) HTTP responses
|
29
|
-
|
30
|
-
## Quickstart
|
20
|
+
Create a client
|
31
21
|
```ruby
|
32
|
-
|
33
|
-
client = Zuora::Client.new(username, password)
|
34
|
-
# Create a model
|
35
|
-
account = Zuora::Models::Account.new(...)
|
36
|
-
serializer = Zuora::Serializers::Attribute
|
37
|
-
# Low level HTTP API
|
38
|
-
client.get('/rest/v1/accounts', serializer.serialze account)
|
39
|
-
# High Level Resource API
|
40
|
-
Zuora::Resources::Account.create! client, account, serializer
|
22
|
+
client = Zuora::Client.new(<username>, <password>)
|
41
23
|
```
|
42
|
-
## Key Features & Concepts
|
43
|
-
1. ***Client:*** Create a client by providing username and password.
|
44
|
-
This authenticates and stores the returned session cookie
|
45
|
-
used in subsequent requests. An optional third, truthy value enables Sandbox instead of production mode.
|
46
24
|
|
47
|
-
|
48
|
-
Use `client.<get|post|put>(url, params)` to make HTTP requests via the authenticated client. Request and response body will be converted to/from Ruby via `farraday_middleware`.
|
25
|
+
Execute a SOAP request. All Zuora calls are supported: `.create()`, `.update()`, `.amend()`, `.generate()`, `.delete()`, `subscribe()`.
|
49
26
|
|
50
|
-
|
51
|
-
|
27
|
+
### Quick Reference
|
28
|
+
See examples below and integration specs for full interfaces.
|
29
|
+
```ruby
|
30
|
+
client.call! :query, "SELECT Id FROM Account"
|
31
|
+
client.call! :create, type: :Account, objects: [{}, {}]
|
32
|
+
client.call! :update, type: :Invoice, objects: [{ id: '123' }, { id: '123' }]
|
33
|
+
client.call! :delete, ids: ['123', '456']
|
34
|
+
client.call! :amend, amendments: {}, amend_options: {}, :preview_options: {}
|
35
|
+
client.call! :subscribe,
|
36
|
+
account: {}
|
37
|
+
payment_method: {}
|
38
|
+
bill_to_contact: {}
|
39
|
+
sold_to_contact: {}
|
40
|
+
subscribe_options: {}
|
41
|
+
subscription: {}
|
42
|
+
rate_plan: {}
|
43
|
+
```
|
52
44
|
|
53
|
-
|
54
|
-
- ex. `Zuora::Serializers::Attribute.serialize account`
|
45
|
+
#### Create Example
|
55
46
|
|
56
|
-
|
47
|
+
```ruby
|
48
|
+
response = client.call! :create,
|
49
|
+
type: :BillRun,
|
50
|
+
objects: [{
|
51
|
+
invoice_date: '2016-03-01',
|
52
|
+
target_date: '2016-03-01'
|
53
|
+
}]
|
54
|
+
```
|
55
|
+
This would generate SOAP XML, make, and return an authenticated SOAP request.
|
56
|
+
```xml
|
57
|
+
<?xml version="1.0"?>
|
58
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:api="http://api.zuora.com/" xmlns:obj="http://object.api.zuora.com/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
59
|
+
<soapenv:Header>
|
60
|
+
<api:SessionHeader>
|
61
|
+
<api:session><!-- SESSION TOKEN HERE --></api:session>
|
62
|
+
</api:SessionHeader>
|
63
|
+
</soapenv:Header>
|
64
|
+
<soapenv:Body>
|
65
|
+
<api:create>
|
66
|
+
<api:zObjects xsi:type="obj:BillRun">
|
67
|
+
<obj:InvoiceDate>2016-03-01</obj:InvoiceDate>
|
68
|
+
<obj:TargetDate>2016-03-01</obj:TargetDate>
|
69
|
+
</api:zObjects>
|
70
|
+
</api:create>
|
71
|
+
</soapenv:Body>
|
72
|
+
</soapenv:Envelope>
|
73
|
+
```
|
57
74
|
|
58
|
-
|
75
|
+
A response object is returned. You can access the raw response:
|
59
76
|
```ruby
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
77
|
+
response.raw.body
|
78
|
+
```
|
79
|
+
```xml
|
80
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
81
|
+
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
82
|
+
<soapenv:Body>
|
83
|
+
<api:createResponse xmlns:api="http://api.zuora.com/">
|
84
|
+
<api:result>
|
85
|
+
<api:Id>2c92c0f9526913e301526a7863df4647</api:Id>
|
86
|
+
<api:Success>true</api:Success>
|
87
|
+
</api:result>
|
88
|
+
</api:createResponse>
|
89
|
+
</soapenv:Body>
|
90
|
+
</soapenv:Envelope>
|
65
91
|
```
|
66
|
-
|
67
|
-
|
68
|
-
## Models
|
69
|
-
Models implement (recursive, nested) Zuora validations using `ActiveModel::Model` and soon, dirty attribute tracking via `ActiveModel::Dirty`
|
70
|
-
* Account
|
71
|
-
* CardHolder
|
72
|
-
* Contact
|
73
|
-
* PaymentMethod::CreditCard
|
74
|
-
* RatePlan
|
75
|
-
* RatePlanCharge
|
76
|
-
* Subscription
|
77
|
-
* Tier
|
78
|
-
|
79
|
-
## Resources
|
80
|
-
In module `Zuora::Resources::`
|
81
|
-
* `Account.create!` **[working]**
|
82
|
-
* `Account.update!` **[working]**
|
83
|
-
* `Subscription.create!` **[working]**
|
84
|
-
* `Subscription.update!` **[working]**
|
85
|
-
* `Subscription.cancel!` [in progress]
|
86
|
-
* `PaymentMethod.update!` [in progress]
|
87
|
-
|
88
|
-
## Examples
|
89
|
-
### Creating an Account
|
92
|
+
|
93
|
+
The `.to_h` method provides an interface that can be navigated as a hash or object.
|
90
94
|
|
91
95
|
```ruby
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
:
|
102
|
-
:
|
103
|
-
:
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
:state => 'FL',
|
109
|
-
:zip_code => '90210',
|
110
|
-
:country => 'US'
|
111
|
-
),
|
112
|
-
:sold_to_contact => Zuora::Models::Contact.new(
|
113
|
-
:first_name => 'Abc',
|
114
|
-
:last_name => 'Def',
|
115
|
-
:country => 'US'
|
116
|
-
),
|
117
|
-
:credit_card => Zuora::Models::PaymentMethod.new(
|
118
|
-
:card_type => 'Visa',
|
119
|
-
:card_number => '4111111111111111',
|
120
|
-
:expiration_month => '03',
|
121
|
-
:expiration_year => '2017',
|
122
|
-
:security_code => '122',
|
123
|
-
)
|
124
|
-
)
|
125
|
-
|
126
|
-
# Create an account in one of two ways:
|
127
|
-
|
128
|
-
serializer = Zuora::Serializers::Attribute
|
129
|
-
|
130
|
-
# Using the low-level API exposed by `Client`
|
131
|
-
response = client.post('/rest/v1/accounts', serializer.serialize accont)
|
132
|
-
|
133
|
-
# or using the higher-level resource API
|
134
|
-
response = Zuora::Resources::Accounts.create!(client, account, serializer)
|
135
|
-
|
136
|
-
# Le response
|
137
|
-
|
138
|
-
pp response
|
139
|
-
|
140
|
-
#<Faraday::Response:0x007f8033b05f08
|
141
|
-
@env=
|
142
|
-
#<struct Faraday::Env
|
143
|
-
method=:post,
|
144
|
-
body=
|
145
|
-
{"success"=>true,
|
146
|
-
"accountId"=>"2c92c0fa521b466c0152250822741a71",
|
147
|
-
"accountNumber"=>"A00000038",
|
148
|
-
"paymentMethodId"=>"2c92c0fa521b466c0152250829c81a7b"},
|
149
|
-
url=#<URI::HTTPS https://apisandbox-api.zuora.com/rest/v1/accounts>,
|
150
|
-
request=
|
151
|
-
#<struct Faraday::RequestOptions
|
152
|
-
params_encoder=nil,
|
153
|
-
proxy=nil,
|
154
|
-
bind=nil,
|
155
|
-
timeout=nil,
|
156
|
-
open_timeout=nil,
|
157
|
-
boundary=nil,
|
158
|
-
oauth=nil>,
|
159
|
-
request_headers=
|
160
|
-
{"User-Agent"=>"Faraday v0.9.2",
|
161
|
-
"Content-Type"=>"application/json",
|
162
|
-
"Cookie"=>
|
163
|
-
"ZSession=LBToVw72ZCAQLjdZ9Ksj8rx2BlP3NbgmMYwCzuf_slSJqIhMbJjdQ1T-4otbdfjUOImQ_XJOCbJgdCd7jHmGsnnJyG49NyRkI7FVKOukVQtdJssJ5n1xAXJeVjxj3qj97iiIZp697v3G2w86iCTN6kWycUlSVezBElbC8_EhScbx8YmaP4QJxXRIFHHdOQPq3IN-9ezk21Cpq3fdXn6s0fIPMU7NUFj7-kD4dcYNBAyd7i2fJVAIV31mXNBH2MuU;"},
|
164
|
-
ssl=
|
165
|
-
#<struct Faraday::SSLOptions
|
166
|
-
verify=false,
|
167
|
-
ca_file=nil,
|
168
|
-
ca_path=nil,
|
169
|
-
verify_mode=nil,
|
170
|
-
cert_store=nil,
|
171
|
-
client_cert=nil,
|
172
|
-
client_key=nil,
|
173
|
-
certificate=nil,
|
174
|
-
private_key=nil,
|
175
|
-
verify_depth=nil,
|
176
|
-
version=nil>,
|
177
|
-
parallel_manager=nil,
|
178
|
-
params=nil,
|
179
|
-
response=#<Faraday::Response:0x007f8033b05f08 ...>,
|
180
|
-
response_headers=
|
181
|
-
{"server"=>"Zuora App",
|
182
|
-
"content-type"=>"application/json;charset=utf-8",
|
183
|
-
"expires"=>"Sat, 09 Jan 2016 06:17:18 GMT",
|
184
|
-
"cache-control"=>"max-age=0, no-cache, no-store",
|
185
|
-
"pragma"=>"no-cache",
|
186
|
-
"date"=>"Sat, 09 Jan 2016 06:17:18 GMT",
|
187
|
-
"content-length"=>"165",
|
188
|
-
"connection"=>"close",
|
189
|
-
"set-cookie"=>
|
190
|
-
"ZSession=dOz9WgdPQbb9J9wzwhuR_t1j9feD4dYBUEZ_sjK6pS9KAaJtPdKN-jAivNELsaANWMJrvHW_1eLxT7XqzjLVBJKzLDJT7_0ucvzcrwNcwMW8mUGpeUhQQu_h2HzNH1kZjc1HX6pfw-BH66BafLemLIdqL75ifmglk8YuTOf_wTg54GsovkrgJCAp9zferw6pYHkZoQUXyH7zmUmmWvMAZ1ZVamhLOf1P3FrrHaw6eIiUj0ehlKvrtxB-GHIgYxh6; Path=/; Secure; HttpOnly"},
|
191
|
-
status=200>,
|
192
|
-
@on_complete_callbacks=[]>
|
96
|
+
response.to_h.envelope.body.create_response.result
|
97
|
+
=> { id: '2c92c0f9526913e301526a7863df4647', success: true }
|
98
|
+
```
|
99
|
+
|
100
|
+
#### .subscribe() example
|
101
|
+
Subscribe is a very large call that involves a lot of data. See the integration spec `spec/zuora/integration/subscription_spec.rb` for full example
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
response = client.call! :subscribe,
|
105
|
+
account: {...},
|
106
|
+
payment_method: {...},
|
107
|
+
bill_to_contact: {...},
|
108
|
+
sold_to_contact: {...},
|
109
|
+
subscription: {...},
|
110
|
+
rate_plan: {...}
|
111
|
+
|
193
112
|
```
|
113
|
+
|
194
114
|
# Changelog
|
195
115
|
* **[0.1.0 - 2016-01-12]** Initial release
|
196
116
|
* **[0.2.0] - 2016-01-14]** Models
|
@@ -205,6 +125,15 @@ pp response
|
|
205
125
|
- Adds VCR for mocking out HTTP requests
|
206
126
|
- Adds integration specs for `Subscribe` `create!` and `update!` and `Account` `create!` and `update!`
|
207
127
|
|
128
|
+
* **[0.3.0 2016-1-28]** Focus on SOAP API, simpify client library feature set
|
129
|
+
- Redesign API, eliminate previous Model constructs
|
130
|
+
- Implement SOAP API Client, as it provides fuller functionality than REST
|
131
|
+
- Focus on constructing + composing hash-like Ruby objects into XML SOAP requests
|
132
|
+
- Add support for custom fields
|
133
|
+
- Remove object-level validations; relies on Zuora's own error responses. Light validations on call constructors.
|
134
|
+
- Provide object/hash lookup capabilities on Zuora Responses
|
135
|
+
- See integration specs for full interface
|
136
|
+
|
208
137
|
# Commit rights
|
209
138
|
Anyone who has a patch accepted may request commit rights. Please do so inside the pull request post-merge.
|
210
139
|
|
data/bin/console
CHANGED
@@ -12,10 +12,10 @@ require 'zuora'
|
|
12
12
|
# Pry.start
|
13
13
|
|
14
14
|
require 'factory_girl'
|
15
|
-
FactoryGirl.definition_file_paths = ['spec/
|
15
|
+
FactoryGirl.definition_file_paths = ['spec/factories']
|
16
16
|
FactoryGirl.find_definitions
|
17
17
|
# Short hand factory girl syntax
|
18
|
-
include FactoryGirl::Syntax::Methods
|
18
|
+
# include FactoryGirl::Syntax::Methods
|
19
19
|
|
20
20
|
require 'irb'
|
21
21
|
IRB.start
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'byebug'
|
2
|
+
|
3
|
+
module SchemaModel
|
4
|
+
def self.included(base)
|
5
|
+
base.include InstanceMethods
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Dynamically configures accessors, dirty tracking, validation,
|
11
|
+
# and serialization methods given definition in opts
|
12
|
+
# @param [Object] _name - name of schema
|
13
|
+
# @param [Hash] opts - See below
|
14
|
+
#
|
15
|
+
# class AwesomeClass
|
16
|
+
# schema :my_schema,
|
17
|
+
# id: {
|
18
|
+
# type: Numeric, # value will be checked using is_a?
|
19
|
+
# valid: -> (v) { v > 0 }, # value will be validated by calling this
|
20
|
+
# schema: [ChildClass] # single or collection recursive checks
|
21
|
+
# doc: 'Id, number greater than 1' # documentation string
|
22
|
+
# }
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# a = AwesomeClass.new(id: 1)
|
26
|
+
# a.valid? => true
|
27
|
+
# a.errors => {}
|
28
|
+
#
|
29
|
+
def schema(_name, opts = {})
|
30
|
+
define_method(:definition) { opts }
|
31
|
+
|
32
|
+
opts.each do |k, definition|
|
33
|
+
# Reader
|
34
|
+
attr_reader k
|
35
|
+
|
36
|
+
# Writer
|
37
|
+
define_writer! k, definition
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# Helper for dynamically defining writer method
|
44
|
+
# @param [Symbol] k - name of attribute
|
45
|
+
# @param [Hash] definition - See docstring for schema above
|
46
|
+
def define_writer!(k, definition)
|
47
|
+
define_method("#{k}=") do |value|
|
48
|
+
# Recursively convert hash and array of hash to schematized objects
|
49
|
+
value = ensure_schema value, definition[:schema]
|
50
|
+
|
51
|
+
# Initial value
|
52
|
+
instance_variable_set "@#{k}", value
|
53
|
+
|
54
|
+
# Dirty tracking
|
55
|
+
self.changed_attributes ||= Set.new
|
56
|
+
self.changed_attributes << k
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
module InstanceMethods
|
62
|
+
attr_accessor :changed_attributes
|
63
|
+
|
64
|
+
def initialize(attrs = {})
|
65
|
+
attrs.each do |attr, v|
|
66
|
+
send("#{attr}=", v)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def errors
|
71
|
+
check definition, self
|
72
|
+
end
|
73
|
+
|
74
|
+
def valid?
|
75
|
+
errors.empty?
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_json
|
79
|
+
return nil unless changed_attributes
|
80
|
+
Hash[
|
81
|
+
changed_attributes.map { |attr| serialize_attr(attr) }
|
82
|
+
]
|
83
|
+
end
|
84
|
+
|
85
|
+
private
|
86
|
+
|
87
|
+
# @param [Symbol] attr
|
88
|
+
# @return [Array]
|
89
|
+
def serialize_attr(attr)
|
90
|
+
value = send(attr)
|
91
|
+
value = if value.is_a?(Hash) || value.is_a?(SchemaModel)
|
92
|
+
value.to_json
|
93
|
+
elsif value.is_a?(Array)
|
94
|
+
value.map(&:to_json)
|
95
|
+
else
|
96
|
+
value
|
97
|
+
end
|
98
|
+
|
99
|
+
[attr.to_s.camelize(:lower), value]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Given a schema and a value which may be a single record or collection,
|
103
|
+
# collect and return any errors.
|
104
|
+
# @param [SchemaModel] child_schema - A schema object class
|
105
|
+
# @param [Object] value - Array of models or single model
|
106
|
+
# @return [Object] Array of errors hashes, or one hash.
|
107
|
+
# Structure matches 'value' input
|
108
|
+
def check_children(child_schema, value)
|
109
|
+
return unless child_schema && value.present?
|
110
|
+
|
111
|
+
if value.is_a? Array
|
112
|
+
value.map(&:errors).reject(&:empty?)
|
113
|
+
else
|
114
|
+
value.errors
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Checks that value is of correct type
|
119
|
+
# @param [Maybe Class] type - type to check using value.is_a?(type)
|
120
|
+
# @param [Object] value - value to check
|
121
|
+
# @return [Maybe String] error message
|
122
|
+
def check_type(type, value)
|
123
|
+
return unless type && value && !value.is_a?(type)
|
124
|
+
|
125
|
+
"should be of type #{type} but is of type #{value.class}"
|
126
|
+
end
|
127
|
+
|
128
|
+
# Checks that required field meets validation
|
129
|
+
# @param [Boolean or Callable] valid - callable validation fn or boolean
|
130
|
+
# function will be called with value
|
131
|
+
# @param [Object] value - value to check
|
132
|
+
# @return [Maybe String] error message
|
133
|
+
def check_validation(valid, value)
|
134
|
+
return unless valid && value
|
135
|
+
|
136
|
+
passes_validation = begin
|
137
|
+
valid.call(value)
|
138
|
+
rescue
|
139
|
+
false
|
140
|
+
end
|
141
|
+
passes_validation ? nil : 'is invalid'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Mutates errors, adding in error messages scoped to the attribute and key
|
145
|
+
# @param [Maybe Hash] errors -
|
146
|
+
# @param [Symbol] attr - name of attribute under check
|
147
|
+
# @param [Symbol] key - name of validation step
|
148
|
+
# @param [Object] val - data to append
|
149
|
+
def append!(errors, attr, key, val)
|
150
|
+
return unless val.present?
|
151
|
+
|
152
|
+
errors ||= {}
|
153
|
+
errors[attr] ||= {}
|
154
|
+
errors[attr][key] = val
|
155
|
+
end
|
156
|
+
|
157
|
+
# @param [Hash] schema
|
158
|
+
# @param [Hash|Object] data
|
159
|
+
# @return [Hash]
|
160
|
+
def check(schema, data)
|
161
|
+
schema.reduce({}) do |errors, (attr, defn)|
|
162
|
+
# Destructuring
|
163
|
+
child_schema, type = defn.values_at :schema, :type
|
164
|
+
|
165
|
+
# Get the value for this attribute
|
166
|
+
value = data.send attr
|
167
|
+
|
168
|
+
# Add error messages
|
169
|
+
append! errors, attr, :child, check_children(child_schema, value)
|
170
|
+
append! errors, attr, :type, check_type(type, value)
|
171
|
+
errors
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Constructs new instance(s) of provided Schema model from hash or
|
176
|
+
# array of hash values. Allows for modeling of has_one and has_many.
|
177
|
+
# @param [Array of Hashes or SchemaModels] value
|
178
|
+
# @param [SchemaModel] child_schema
|
179
|
+
|
180
|
+
def ensure_schema(value, child_schema)
|
181
|
+
if value.present? && child_schema.present?
|
182
|
+
value = if child_schema.is_a?(Array)
|
183
|
+
value.map do |item|
|
184
|
+
item.is_a?(SchemaModel) ? item : child_schema[0].new(item)
|
185
|
+
end
|
186
|
+
else
|
187
|
+
value.is_a?(SchemaModel) ? value : child_schema.new(value)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
value
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|