thron 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.example +4 -0
- data/.gitignore +13 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/README.md +182 -0
- data/Rakefile +10 -0
- data/Vagrantfile +80 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config/thron.yml +10 -0
- data/lib/thron.rb +3 -0
- data/lib/thron/circuit_breaker.rb +46 -0
- data/lib/thron/config.rb +35 -0
- data/lib/thron/entity/base.rb +78 -0
- data/lib/thron/entity/image.rb +30 -0
- data/lib/thron/gateway/access_manager.rb +69 -0
- data/lib/thron/gateway/apps.rb +91 -0
- data/lib/thron/gateway/apps_admin.rb +153 -0
- data/lib/thron/gateway/base.rb +41 -0
- data/lib/thron/gateway/category.rb +210 -0
- data/lib/thron/gateway/client.rb +59 -0
- data/lib/thron/gateway/comment.rb +76 -0
- data/lib/thron/gateway/contact.rb +120 -0
- data/lib/thron/gateway/content.rb +267 -0
- data/lib/thron/gateway/content_category.rb +32 -0
- data/lib/thron/gateway/content_list.rb +40 -0
- data/lib/thron/gateway/dashboard.rb +120 -0
- data/lib/thron/gateway/delivery.rb +122 -0
- data/lib/thron/gateway/device.rb +58 -0
- data/lib/thron/gateway/metadata.rb +68 -0
- data/lib/thron/gateway/publish_in_weebo_express.rb +39 -0
- data/lib/thron/gateway/publishing_process.rb +141 -0
- data/lib/thron/gateway/repository.rb +111 -0
- data/lib/thron/gateway/session.rb +15 -0
- data/lib/thron/gateway/users_group_manager.rb +117 -0
- data/lib/thron/gateway/v_user_manager.rb +195 -0
- data/lib/thron/logger.rb +25 -0
- data/lib/thron/pageable.rb +26 -0
- data/lib/thron/paginator.rb +82 -0
- data/lib/thron/response.rb +48 -0
- data/lib/thron/root.rb +9 -0
- data/lib/thron/routable.rb +80 -0
- data/lib/thron/route.rb +102 -0
- data/lib/thron/string_extensions.rb +23 -0
- data/lib/thron/user.rb +77 -0
- data/lib/thron/version.rb +3 -0
- data/log/.gitignore +4 -0
- data/thron.gemspec +26 -0
- metadata +176 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 828717f3d643a94ede20ded4f7d5ffec9515244c
|
4
|
+
data.tar.gz: 6ac41767ca932862c830b22933651e49b8d68e45
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a37a51677d948af316337b411a051add6b6d78717fb20d2531076912040559349f4a2ec7023523d12e93ce41eb48fd29c65f0f60cc421e86ea880045ab1bfe30
|
7
|
+
data.tar.gz: 6f21a2b5e729e4cef36063593218acad88c46c838c4ebc37a6bfa687a04823a7d7223d6b4b8f12cbfcd13b841e2e3e025477722824109c25e0bd9df90eade41c
|
data/.env.example
ADDED
@@ -0,0 +1,4 @@
|
|
1
|
+
THRON_CLIENT_ID=<the client id subscribed to Thron APIs>
|
2
|
+
VERBOSE=<a boolean indicating if the remote calls has to be printed on screen, useful when dealing with REPL>
|
3
|
+
LOGGER_LEVEL=<the logger level you want, default to WARN>
|
4
|
+
CIRCUIT_BREAKER_THRESHOLD=<the number of tries before circuit breaker open the circuit in case of communication issues, set to zero to disable the circuit breaker>
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
## Table of Contents
|
2
|
+
* [Warning](#warning)
|
3
|
+
* [Scope](#scope)
|
4
|
+
* [Setup](#setup)
|
5
|
+
* [HTTPArty](#httparty)
|
6
|
+
* [Architecture](#architecture)
|
7
|
+
* [Gateways](#gateways)
|
8
|
+
* [Routing](#routing)
|
9
|
+
* [Paginator](#paginator)
|
10
|
+
* [Response](#response)
|
11
|
+
* [Dynamic Entities](#dynamic-entities)
|
12
|
+
* [User Aggregate](#user-aggregate)
|
13
|
+
* [Interface](#interface)
|
14
|
+
* [Access](#access)
|
15
|
+
* [Contents](#contents)
|
16
|
+
* [Groups](#groups)
|
17
|
+
* [Categories](#categories)
|
18
|
+
* [Disguise](#disguise)
|
19
|
+
|
20
|
+
## Warning
|
21
|
+
I do not maintain this gem anymore. Please do copy with latest Thron APIs and integrate them into this facade if you need to use it.
|
22
|
+
|
23
|
+
## Scope
|
24
|
+
This gem provides a simple Ruby client for the [Thron](https://developer.4me.it/index.php) (ex 4me) APIs.
|
25
|
+
The aim of this gem is to provide a simple interface for your ruby application
|
26
|
+
that need to communicate with Thron services.
|
27
|
+
I've also managed to keep the gem's dependencies footprint as small as possible (i
|
28
|
+
hate bulky Gemfile...).
|
29
|
+
|
30
|
+
## Setup
|
31
|
+
This gem use at least a **Ruby 2.1** compatible parser.
|
32
|
+
Thron is a payment service, is assumed you have a valid account in order to use this gem.
|
33
|
+
Once you have valid credentials to access the Thron APIs, you have to enter the **THRON_CLIENT_ID** value
|
34
|
+
inside of your *.env* file, this way the gem can reads from it and configure itself properly.
|
35
|
+
|
36
|
+
## HTTParty
|
37
|
+
This gem uses the [HTTParty](https://github.com/jnunemaker/httparty) library to communicate with the Thron APIs.
|
38
|
+
HTTParty has proven to be reliable and simple, so it's a natural candidate.
|
39
|
+
|
40
|
+
## Architecture
|
41
|
+
This gem is architected on these concepts:
|
42
|
+
|
43
|
+
### Gateways
|
44
|
+
Several gateways objects are used to communicate with the Thron APIs: each of them
|
45
|
+
mimic the original Thron API namespace (find a complete list on Thron site).
|
46
|
+
Each gateway derive from a basic class, which includes the HTTParty interface.
|
47
|
+
|
48
|
+
#### Routing
|
49
|
+
A simple routing engine is encapsulated into the gateway objects: a class-level
|
50
|
+
hash declares the routes that matches the API name, by specifying the
|
51
|
+
format and verb.
|
52
|
+
Some extra parameters are used to factory the route URL, in some cases lazily (by returning a proc object), since some APIs need to access the method arguments early to compose the URL.
|
53
|
+
|
54
|
+
#### Paginator
|
55
|
+
Thron APIs that return a list of results are limited to a maximum of **50**.
|
56
|
+
To avoid repeating the same call many times by passing an augmented offset,
|
57
|
+
is possible to call a wrapper method on the gateway objects that returns a paginator object.
|
58
|
+
Once the paginator is loaded, it allows to navigate the results by using the following interface:
|
59
|
+
* **next**: loads the first offset and move forward, returning last when max offset is reached
|
60
|
+
* **prev**: move backwards from the current offset, returning first when minimum offset is reached
|
61
|
+
* **preload**: does not move the offset, but indeed preload the specified number of
|
62
|
+
data by performing an asynchronous call (wrapped in a thread).
|
63
|
+
|
64
|
+
Paginator keeps an internal cache to avoid hitting the remote service more than
|
65
|
+
once. The same is used when preloading results. Keep that in mind when you need to get fresh data.
|
66
|
+
|
67
|
+
### Response
|
68
|
+
The HTTParty response has been wrapped in order to return a logical object that wraps the APIs return values.
|
69
|
+
The main attributes are:
|
70
|
+
* http_code: the HTTP code of the response
|
71
|
+
* body: the body of the response, in case the result is JSON data, it contains the data parsed into an appropriate entity (read below)
|
72
|
+
* total: in case the API returns a list of results, it indicates the total number of records; it's used by paginator
|
73
|
+
* error: the error message, if any, returned by the API
|
74
|
+
|
75
|
+
### Dynamic Entities
|
76
|
+
Some of the Thron APIs return a JSON representation of an entity. I have initially
|
77
|
+
considered wrapping each entity into its own PORO object, but gave up after few
|
78
|
+
days since the quality of the returned entities is very large.
|
79
|
+
I opted instead to wrap returned data into a sub-class of the OpenStruct object, having the same
|
80
|
+
dynamic behaviour while adding some sugar features:
|
81
|
+
* it converts the lower-camel-case parameters into (more rubesque) snake-case
|
82
|
+
* it does recursive mapping of nested attributes
|
83
|
+
* it converts time and date values to appropriate Ruby objects
|
84
|
+
The same object can be used when passing complex parameters to some of the APIs: in this case is sufficent to call the *#to_payload* method on the entity
|
85
|
+
to convert the object in an hash with lower-camle-case keys (see examples below).
|
86
|
+
|
87
|
+
### User Aggregate
|
88
|
+
To avoid accessing the multitude of Thron APIs via several objects, an aggregate has been created (DDD anyone?).
|
89
|
+
The **User** aggregate delegates most of its methods directly to the gateway objects (it uses the *Forwardable* module).
|
90
|
+
It keeps a registry of the gateway objects in order to refresh them on login: this
|
91
|
+
is requested to update the token id that identifies the current session and that is
|
92
|
+
stored internally by the gateway objects.
|
93
|
+
|
94
|
+
To create the user aggregate simply instantiate it without arguments:
|
95
|
+
```ruby
|
96
|
+
user = Thron::User::new
|
97
|
+
```
|
98
|
+
|
99
|
+
## Interface
|
100
|
+
The following examples illustrates how to use this library.
|
101
|
+
Thron APIs include a broad range of methods, for a complete list of them please consult the [official APIs documentation](https://developer.thron.com/index.php).
|
102
|
+
For the APIs that accepts composed arguments simply use the dynamic entity described
|
103
|
+
above: just be aware to name its attributes by snake-case and not as the
|
104
|
+
lower-camel-case specified in the Thron documentation.
|
105
|
+
|
106
|
+
### Access
|
107
|
+
To get a valid session token just login with your credentials:
|
108
|
+
```ruby
|
109
|
+
user.login(username: '<your_username>', password: '<your_password>')
|
110
|
+
```
|
111
|
+
From here you can check current user with the available methods, for example:
|
112
|
+
```ruby
|
113
|
+
user.validate_token
|
114
|
+
```
|
115
|
+
Or you can query the APIs to return other details:
|
116
|
+
```ruby
|
117
|
+
user.user_detail(username: '<a_username>')
|
118
|
+
```
|
119
|
+
Uploads the avatar image for a user (it relies on Linux *file* system call):
|
120
|
+
```ruby
|
121
|
+
avatar = Thron::Entity::Image::new(path: '<path_to_an_image>').to_payload
|
122
|
+
user.update_image(username: '<a_username>', image: avatar)
|
123
|
+
```
|
124
|
+
|
125
|
+
### Contents
|
126
|
+
Thron is all about managing users contents, so no surprise there is a plethora of
|
127
|
+
methods at your disposal:
|
128
|
+
|
129
|
+
Find the contents by using the paginator object:
|
130
|
+
```ruby
|
131
|
+
paginator = user.find_contents_paginator
|
132
|
+
paginator.preload(10) # preload the first 10 calls
|
133
|
+
paginator.next # fetch first result set from preloaded cache
|
134
|
+
```
|
135
|
+
Show the contents by category (slightly more efficient):
|
136
|
+
```ruby
|
137
|
+
user.show_contents(category_id: '<a_category_id>')
|
138
|
+
```
|
139
|
+
Load specific content detail:
|
140
|
+
```ruby
|
141
|
+
user.content_detail(content_id: '<a_content_id>')
|
142
|
+
```
|
143
|
+
|
144
|
+
### Groups
|
145
|
+
Users are arranged into different groups.
|
146
|
+
|
147
|
+
Create a new group:
|
148
|
+
```ruby
|
149
|
+
group = Thron::Entity::Base::new(active: true, name: 'my new group').to_payload
|
150
|
+
user.create_group(data: group)
|
151
|
+
```
|
152
|
+
List existing groups:
|
153
|
+
```ruby
|
154
|
+
paginator = user.find_gropus
|
155
|
+
paginator.next
|
156
|
+
```
|
157
|
+
|
158
|
+
### Categories
|
159
|
+
Thron contents are organized by categories.
|
160
|
+
|
161
|
+
List existing categories (without paginator):
|
162
|
+
```ruby
|
163
|
+
user.find_categories
|
164
|
+
```
|
165
|
+
Create a new locale for a category:
|
166
|
+
```ruby
|
167
|
+
locale = Thron::Entity::Base::new(name: 'photos', description: 'JPG and PNG images', locale: 'EN').to_payload
|
168
|
+
user.create_category_locale(category_id: '<a_category_id>', locale: locale)
|
169
|
+
```
|
170
|
+
|
171
|
+
### Disguise
|
172
|
+
Thron APIs allow to disguise another user via its apps sub-system.
|
173
|
+
|
174
|
+
Disguising only works inside the block:
|
175
|
+
```ruby
|
176
|
+
user.disguise(app_id: '<app_id_that_can_disguise>', username: '<username_to_disguise>') do
|
177
|
+
# load the disguised user contents, each gateway will now use the disguised token id
|
178
|
+
contents = user.find_contents
|
179
|
+
# do something with contents
|
180
|
+
end
|
181
|
+
# finished disguising, it returns disguised token id
|
182
|
+
```
|
data/Rakefile
ADDED
data/Vagrantfile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# -*- mode: ruby -*-
|
2
|
+
# vi: set ft=ruby :
|
3
|
+
|
4
|
+
# All Vagrant configuration is done below. The "2" in Vagrant.configure
|
5
|
+
# configures the configuration version (we support older styles for
|
6
|
+
# backwards compatibility). Please don't change it unless you know what
|
7
|
+
# you're doing.
|
8
|
+
Vagrant.configure(2) do |config|
|
9
|
+
# The most common configuration options are documented and commented below.
|
10
|
+
# For a complete reference, please see the online documentation at
|
11
|
+
# https://docs.vagrantup.com.
|
12
|
+
|
13
|
+
# Every Vagrant development environment requires a box. You can search for
|
14
|
+
# boxes at https://atlas.hashicorp.com/search.
|
15
|
+
config.vm.box = "ubuntu/trusty64"
|
16
|
+
|
17
|
+
# Disable automatic box update checking. If you disable this, then
|
18
|
+
# boxes will only be checked for updates when the user runs
|
19
|
+
# `vagrant box outdated`. This is not recommended.
|
20
|
+
config.vm.box_check_update = false
|
21
|
+
|
22
|
+
# Create a forwarded port mapping which allows access to a specific port
|
23
|
+
# within the machine from a port on the host machine. In the example below,
|
24
|
+
# accessing "localhost:8080" will access port 80 on the guest machine.
|
25
|
+
# config.vm.network "forwarded_port", guest: 80, host: 8080
|
26
|
+
|
27
|
+
# Create a private network, which allows host-only access to the machine
|
28
|
+
# using a specific IP.
|
29
|
+
config.vm.network "private_network", ip: "192.168.33.22"
|
30
|
+
|
31
|
+
# Create a public network, which generally matched to bridged network.
|
32
|
+
# Bridged networks make the machine appear as another physical device on
|
33
|
+
# your network.
|
34
|
+
# config.vm.network "public_network"
|
35
|
+
|
36
|
+
# Share an additional folder to the guest VM. The first argument is
|
37
|
+
# the path on the host to the actual folder. The second argument is
|
38
|
+
# the path on the guest to mount the folder. And the optional third
|
39
|
+
# argument is a set of non-required options.
|
40
|
+
# config.vm.synced_folder "../data", "/vagrant_data"
|
41
|
+
|
42
|
+
# Provider-specific configuration so you can fine-tune various
|
43
|
+
# backing providers for Vagrant. These expose provider-specific options.
|
44
|
+
# Example for VirtualBox:
|
45
|
+
#
|
46
|
+
config.vm.provider "virtualbox" do |vb|
|
47
|
+
# Display the VirtualBox GUI when booting the machine
|
48
|
+
#vb.gui = true
|
49
|
+
|
50
|
+
# Customize the amount of memory on the VM:
|
51
|
+
vb.memory = "6144"
|
52
|
+
vb.cpus = 3
|
53
|
+
end
|
54
|
+
#
|
55
|
+
# View the documentation for the provider you are using for more
|
56
|
+
# information on available options.
|
57
|
+
|
58
|
+
# Define a Vagrant Push strategy for pushing to Atlas. Other push strategies
|
59
|
+
# such as FTP and Heroku are also available. See the documentation at
|
60
|
+
# https://docs.vagrantup.com/v2/push/atlas.html for more information.
|
61
|
+
# config.push.define "atlas" do |push|
|
62
|
+
# push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME"
|
63
|
+
# end
|
64
|
+
|
65
|
+
# Enable provisioning with a shell script. Additional provisioners such as
|
66
|
+
# Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
|
67
|
+
# documentation for more information about their specific syntax and use.
|
68
|
+
# config.vm.provision "shell", inline: <<-SHELL
|
69
|
+
# sudo apt-get update
|
70
|
+
# sudo apt-get install -y apache2
|
71
|
+
# SHELL
|
72
|
+
$script = [
|
73
|
+
"sudo apt-add-repository ppa:brightbox/ruby-ng",
|
74
|
+
"sudo apt-get update",
|
75
|
+
"sudo apt-get -y -q install build-essential libssl-dev git",
|
76
|
+
"sudo apt-get -y -q install ruby2.3 ruby2.3-dev",
|
77
|
+
"sudo gem install bundler"]
|
78
|
+
|
79
|
+
config.vm.provision "shell", inline: $script.join(" && ")
|
80
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "thron"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/config/thron.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
logger:
|
2
|
+
level: <%= ENV.fetch('LOGGER_LEVEL') { 'warn' } %>
|
3
|
+
verbose: <%= ENV.fetch('VERBOSE') { false } %>
|
4
|
+
|
5
|
+
circuit_breaker:
|
6
|
+
threshold: <%= ENV.fetch('CIRCUIT_BREAKER_THRESHOLD') { 0 } %>
|
7
|
+
|
8
|
+
thron:
|
9
|
+
client_id: <%= ENV.fetch('THRON_CLIENT_ID') { 'none' } %>
|
10
|
+
protocol: <%= ENV.fetch('THRON_PROTOCOL') { 'https' } %>
|
data/lib/thron.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Thron
|
2
|
+
class CircuitBreaker
|
3
|
+
class OpenError < StandardError; end
|
4
|
+
|
5
|
+
%w[open closed].each do |name|
|
6
|
+
const_set(name.upcase.to_sym, name.to_sym)
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
@state = CLOSED
|
11
|
+
@threshold = options.fetch(:threshold) { 5 }
|
12
|
+
@error_count = 0
|
13
|
+
@ignored = options[:ignored].to_a
|
14
|
+
end
|
15
|
+
|
16
|
+
def monitor
|
17
|
+
return yield if @threshold.zero?
|
18
|
+
fail OpenError, 'the circuit breaker is open!' if open?
|
19
|
+
result = yield
|
20
|
+
handle_success
|
21
|
+
result
|
22
|
+
rescue OpenError
|
23
|
+
raise
|
24
|
+
rescue => error
|
25
|
+
handle_error(error) unless @ignored.include?(error.class)
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
|
29
|
+
def open?
|
30
|
+
@state == OPEN
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def handle_success
|
36
|
+
@error_count = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
def handle_error(error)
|
40
|
+
@error_count += 1
|
41
|
+
if @error_count >= @threshold
|
42
|
+
@state = OPEN
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/thron/config.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
require 'dotenv'
|
4
|
+
require 'ostruct'
|
5
|
+
require 'logger'
|
6
|
+
require 'thron/root'
|
7
|
+
|
8
|
+
module Thron
|
9
|
+
module Config
|
10
|
+
extend self
|
11
|
+
|
12
|
+
CONFIG_YML = Thron::root.join('config', 'thron.yml')
|
13
|
+
|
14
|
+
def dump_yaml
|
15
|
+
Dotenv.load
|
16
|
+
@yaml ||= YAML.load(ERB.new(File.read(CONFIG_YML)).result)
|
17
|
+
end
|
18
|
+
|
19
|
+
def logger
|
20
|
+
@logger ||= begin
|
21
|
+
level = dump_yaml.fetch('logger').fetch('level')
|
22
|
+
verbose = dump_yaml.fetch('logger').fetch('verbose')
|
23
|
+
OpenStruct.new(level: Logger::const_get(level.upcase), verbose: verbose)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def circuit_breaker
|
28
|
+
@circuit_breaker ||= OpenStruct.new(dump_yaml['circuit_breaker'])
|
29
|
+
end
|
30
|
+
|
31
|
+
def thron
|
32
|
+
@thron ||= OpenStruct.new(dump_yaml['thron'])
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'time'
|
3
|
+
require 'thron/string_extensions'
|
4
|
+
|
5
|
+
module Thron
|
6
|
+
using StringExtensions
|
7
|
+
module Entity
|
8
|
+
class Base < OpenStruct
|
9
|
+
TIME_REGEX = /\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.+/
|
10
|
+
DATE_REGEX = /\A\d{4}-\d{2}-\d{2}/
|
11
|
+
|
12
|
+
def self.factory(args)
|
13
|
+
case args
|
14
|
+
when Hash
|
15
|
+
new(args)
|
16
|
+
when Array
|
17
|
+
args.map { |data| new(data) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(hash = {})
|
22
|
+
@table = {}
|
23
|
+
hash.each do |k,v|
|
24
|
+
k = k.to_s.snakecase.to_sym
|
25
|
+
@table[k] = case v
|
26
|
+
when Hash
|
27
|
+
self.class.new(v)
|
28
|
+
when Array
|
29
|
+
if v.first.is_a?(Hash)
|
30
|
+
v.map { |e| self.class.new(e) }
|
31
|
+
else
|
32
|
+
v
|
33
|
+
end
|
34
|
+
when TIME_REGEX
|
35
|
+
Time::parse(v)
|
36
|
+
when DATE_REGEX
|
37
|
+
Date::parse(v)
|
38
|
+
else
|
39
|
+
v
|
40
|
+
end
|
41
|
+
new_ostruct_member(k)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_h
|
46
|
+
self.each_pair.reduce({}) do |acc, (k,v)|
|
47
|
+
acc[k] = fetch_value(v, :to_h); acc
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def to_payload
|
52
|
+
self.each_pair.reduce({}) do |acc, (k,v)|
|
53
|
+
k = k.to_s.camelize_low
|
54
|
+
acc[k] = fetch_value(v, :to_payload); acc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def fetch_value(value, message)
|
61
|
+
case value
|
62
|
+
when Base
|
63
|
+
value.send(message)
|
64
|
+
when Array
|
65
|
+
if value.first.is_a?(Base)
|
66
|
+
value.map { |entity| entity.send(message) }
|
67
|
+
else
|
68
|
+
value
|
69
|
+
end
|
70
|
+
when Date, Time
|
71
|
+
value.iso8601
|
72
|
+
else
|
73
|
+
value
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|