zuora-ruby 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Code Climate](https://codeclimate.com/repos/569444dfa3d810003a00313f/badges/416bae00acf65d690efe/gpa.svg)](https://codeclimate.com/repos/569444dfa3d810003a00313f/feed)
|
3
3
|
[![Test Coverage](https://codeclimate.com/repos/569444dfa3d810003a00313f/badges/416bae00acf65d690efe/coverage.svg)](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
|