thron 0.7.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/.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
|