kounta_rest 0.1.7 → 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -36
- data/lib/kounta/address.rb +14 -17
- data/lib/kounta/break.rb +6 -0
- data/lib/kounta/category.rb +12 -15
- data/lib/kounta/company.rb +59 -50
- data/lib/kounta/customer.rb +33 -32
- data/lib/kounta/errors.rb +8 -7
- data/lib/kounta/inventory.rb +5 -7
- data/lib/kounta/line.rb +18 -21
- data/lib/kounta/order.rb +45 -49
- data/lib/kounta/payment.rb +14 -17
- data/lib/kounta/person.rb +21 -24
- data/lib/kounta/price_list.rb +7 -9
- data/lib/kounta/product.rb +25 -27
- data/lib/kounta/register.rb +11 -14
- data/lib/kounta/resource.rb +105 -93
- data/lib/kounta/rest/client.rb +177 -79
- data/lib/kounta/shift.rb +25 -0
- data/lib/kounta/site.rb +46 -38
- data/lib/kounta/staff.rb +30 -0
- data/lib/kounta/tax.rb +7 -9
- data/lib/kounta/version.rb +1 -1
- data/lib/kounta/webhook.rb +16 -0
- data/lib/kounta.rb +42 -72
- metadata +77 -145
- data/.gitignore +0 -20
- data/.gitmodules +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -1
- data/console.rb +0 -14
- data/kounta.gemspec +0 -33
- data/spec/fixtures/address.json +0 -12
- data/spec/fixtures/addresses.json +0 -25
- data/spec/fixtures/base_price_list.json +0 -52
- data/spec/fixtures/categories.json +0 -217
- data/spec/fixtures/category.json +0 -11
- data/spec/fixtures/companies_me.json +0 -45
- data/spec/fixtures/customer.json +0 -39
- data/spec/fixtures/customers.json +0 -26
- data/spec/fixtures/inventory.json +0 -10
- data/spec/fixtures/line.json +0 -7
- data/spec/fixtures/order.json +0 -67
- data/spec/fixtures/orders.json +0 -134
- data/spec/fixtures/payment.json +0 -6
- data/spec/fixtures/people.json +0 -20
- data/spec/fixtures/person.json +0 -41
- data/spec/fixtures/price_list.json +0 -52
- data/spec/fixtures/price_lists.json +0 -12
- data/spec/fixtures/product.json +0 -25
- data/spec/fixtures/products.json +0 -119
- data/spec/fixtures/site.json +0 -6
- data/spec/fixtures/sites.json +0 -14
- data/spec/fixtures/tax.json +0 -7
- data/spec/fixtures/taxes.json +0 -16
- data/spec/helper.rb +0 -28
- data/spec/kounta/address_spec.rb +0 -11
- data/spec/kounta/category_spec.rb +0 -11
- data/spec/kounta/company_spec.rb +0 -24
- data/spec/kounta/customer_spec.rb +0 -15
- data/spec/kounta/kounta_spec.rb +0 -21
- data/spec/kounta/line_spec.rb +0 -14
- data/spec/kounta/order_spec.rb +0 -37
- data/spec/kounta/payment_spec.rb +0 -14
- data/spec/kounta/person_spec.rb +0 -15
- data/spec/kounta/product_spec.rb +0 -16
- data/spec/kounta/resource_spec.rb +0 -95
- data/spec/kounta/rest/client_spec.rb +0 -38
- data/spec/kounta/site_spec.rb +0 -11
- data/spec/support/endpoints.rb +0 -72
data/lib/kounta/price_list.rb
CHANGED
@@ -1,10 +1,8 @@
|
|
1
1
|
module Kounta
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
end
|
2
|
+
class PriceList < Kounta::Resource
|
3
|
+
property :parent_id
|
4
|
+
property :name
|
5
|
+
property :products
|
6
|
+
coerce_key :products, Product
|
7
|
+
end
|
8
|
+
end
|
data/lib/kounta/product.rb
CHANGED
@@ -1,31 +1,29 @@
|
|
1
1
|
module Kounta
|
2
|
+
class Product < Kounta::Resource
|
3
|
+
property :company_id
|
4
|
+
property :code
|
5
|
+
property :barcode
|
6
|
+
property :stock
|
7
|
+
property :name
|
8
|
+
property :description
|
9
|
+
property :tags
|
10
|
+
property :image
|
11
|
+
property :unit_price
|
12
|
+
property :cost_price
|
13
|
+
property :taxes
|
14
|
+
property :sites
|
15
|
+
property :number
|
2
16
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
property :barcode
|
7
|
-
property :stock
|
8
|
-
property :name
|
9
|
-
property :description
|
10
|
-
property :tags
|
11
|
-
property :image
|
12
|
-
property :unit_price
|
13
|
-
property :cost_price
|
14
|
-
property :taxes
|
15
|
-
property :sites
|
16
|
-
property :number
|
17
|
+
has_many(:categories, Kounta::Category, { company_id: :company_id },
|
18
|
+
proc { |klass| { companies: klass.company_id, products: klass.id, categories: nil } })
|
19
|
+
coerce_key :taxes, Kounta::Tax
|
17
20
|
|
18
|
-
|
19
|
-
|
21
|
+
def tags_include?(name)
|
22
|
+
tags.any? { |s| s.casecmp(name).zero? }
|
23
|
+
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
{companies: company_id, products: id}
|
27
|
-
end
|
28
|
-
|
29
|
-
end
|
30
|
-
|
31
|
-
end
|
25
|
+
def resource_path
|
26
|
+
{ companies: company_id, products: id }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/kounta/register.rb
CHANGED
@@ -1,15 +1,12 @@
|
|
1
1
|
module Kounta
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
end
|
14
|
-
|
15
|
-
end
|
2
|
+
class Register < Kounta::Resource
|
3
|
+
property :company_id
|
4
|
+
property :code
|
5
|
+
property :name
|
6
|
+
property :site_id
|
7
|
+
|
8
|
+
def resource_path
|
9
|
+
{ companies: company_id, registers: id }
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/kounta/resource.rb
CHANGED
@@ -1,96 +1,108 @@
|
|
1
1
|
require 'hashie'
|
2
|
-
require_relative
|
2
|
+
require_relative 'rest/client'
|
3
3
|
|
4
4
|
module Kounta
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
end
|
5
|
+
class Resource < Hashie::Dash
|
6
|
+
include Hashie::Extensions::Dash::IndifferentAccess if defined?(Hashie::Extensions::Dash::IndifferentAccess)
|
7
|
+
include Hashie::Extensions::Coercion
|
8
|
+
|
9
|
+
attr_accessor :client
|
10
|
+
|
11
|
+
property :id
|
12
|
+
property :created_at
|
13
|
+
property :updated_at
|
14
|
+
|
15
|
+
def self.coerce(data)
|
16
|
+
if data.is_a? Array
|
17
|
+
data.map { |item| new(item) }
|
18
|
+
else
|
19
|
+
new(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.has_one(sym, klass, assignments, route) # rubocop:disable Naming/PredicateName
|
24
|
+
define_method(sym) do |item_id = nil, *args|
|
25
|
+
if item_id
|
26
|
+
assign_into(client.object_from_response(klass, :get, route.call(self, item_id), params: args[0]), self, assignments)
|
27
|
+
else
|
28
|
+
assign_into(klass.new, self, assignments)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.has_many(sym, klass, assignments, route) # rubocop:disable Naming/PredicateName
|
34
|
+
define_method(sym) do |has_many_params = nil, *args|
|
35
|
+
client.objects_from_response(klass, :get, route.call(self, has_many_params), params: args[0])
|
36
|
+
.map { |returned_klass| assign_into(returned_klass, self, assignments) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.has_many_in_time_range(sym, klass, assignments, route) # rubocop:disable Naming/PredicateName
|
41
|
+
define_method(sym) do |has_many_params = nil, *args|
|
42
|
+
client.objects_from_response_in_time_range(klass, :get, route.call(self, has_many_params), params: args[0])
|
43
|
+
.map { |returned_klass| assign_into(returned_klass, self, assignments) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(hash = {})
|
48
|
+
return unless hash
|
49
|
+
|
50
|
+
hash.each_pair do |k, v|
|
51
|
+
begin
|
52
|
+
self[k] = v if respond_to? k.to_sym
|
53
|
+
rescue NoMethodError
|
54
|
+
raise Kounta::Errors::UnknownResourceAttribute, "Unknown attribute: #{k} on resource #{self.class}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def to_hash(hash = {})
|
60
|
+
{}.tap do |returning|
|
61
|
+
self.class.properties.each do |property|
|
62
|
+
next if ignored_properties.include?(property)
|
63
|
+
returning[property] = self[property] if self[property]
|
64
|
+
end
|
65
|
+
end.merge(hash)
|
66
|
+
end
|
67
|
+
|
68
|
+
def save!
|
69
|
+
response = new? ? client.perform(resource_path, :post, body: to_hash) : client.perform(resource_path, :put, body: to_hash)
|
70
|
+
|
71
|
+
# automatically follow redirects to resources
|
72
|
+
response = client.perform(response.headers['location'], :get) if response.status == 201
|
73
|
+
|
74
|
+
response.parsed.each_pair do |k, v|
|
75
|
+
self[k] = v if respond_to? k.to_sym
|
76
|
+
end
|
77
|
+
|
78
|
+
self
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete!
|
82
|
+
return self if new?
|
83
|
+
response = client.perform(resource_path, :delete)
|
84
|
+
|
85
|
+
except!('id', 'created_at', 'updated_at') if response.status == 204
|
86
|
+
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def new?
|
91
|
+
!id
|
92
|
+
end
|
93
|
+
|
94
|
+
def ignored_properties(array = [])
|
95
|
+
array + %i[created_at updated_at id company_id]
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def assign_into(klass, assigner, assignments)
|
101
|
+
klass.client = assigner.client
|
102
|
+
assignments.each_pair do |k, v|
|
103
|
+
klass[k] = assigner[v]
|
104
|
+
end
|
105
|
+
klass
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
data/lib/kounta/rest/client.rb
CHANGED
@@ -3,82 +3,180 @@ require 'oj'
|
|
3
3
|
require 'faraday_middleware'
|
4
4
|
|
5
5
|
module Kounta
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
end
|
6
|
+
module REST
|
7
|
+
class Client
|
8
|
+
def initialize(**options)
|
9
|
+
@redirect_uri = options[:redirect_uri]
|
10
|
+
@consumer = options[:consumer]
|
11
|
+
@access_token = options[:access_token]
|
12
|
+
@refresh_token = options[:refresh_token]
|
13
|
+
@client = OAuth2::Client.new(
|
14
|
+
@consumer[:key], @consumer[:secret],
|
15
|
+
site: Kounta::SITE_URI,
|
16
|
+
authorize_url: Kounta::AUTHORIZATION_URI,
|
17
|
+
token_url: Kounta::TOKEN_URI
|
18
|
+
) do |faraday|
|
19
|
+
faraday.request :json
|
20
|
+
faraday.use Faraday::Request::UrlEncoded
|
21
|
+
faraday.use Faraday::Response::Logger if Kounta.enable_logging
|
22
|
+
faraday.adapter Faraday.default_adapter
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def authenticated?
|
27
|
+
@access_token.present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def get_access_code_url(params = {})
|
31
|
+
# Kounta's API seems to require the `state` param (can't find documentation on it anywhere)
|
32
|
+
# learn more about it: http://homakov.blogspot.com.au/2012/07/saferweb-most-common-oauth2.html
|
33
|
+
@client.auth_code.authorize_url(params.merge(redirect_uri: @redirect_uri, state: SecureRandom.hex(24)))
|
34
|
+
end
|
35
|
+
|
36
|
+
def get_access_token(access_code)
|
37
|
+
@token = @client.auth_code.get_token(access_code, redirect_uri: @redirect_uri)
|
38
|
+
@access_token = @token.token
|
39
|
+
@expires_at = @token.expires_at
|
40
|
+
@refresh_token = @token.refresh_token
|
41
|
+
@token
|
42
|
+
end
|
43
|
+
|
44
|
+
def company(hash = {})
|
45
|
+
@company ||= Kounta::Company.new(self, hash)
|
46
|
+
end
|
47
|
+
|
48
|
+
def path_from_hash(url_hash)
|
49
|
+
# TODO: there's probably a more correct way of doing this encoding
|
50
|
+
url_hash.map { |key, value| value ? "#{key}/#{value.to_s.gsub('-', '%2D')}" : key.to_s }.join('/')
|
51
|
+
end
|
52
|
+
|
53
|
+
def perform(url_hash, request_method, options = {})
|
54
|
+
begin
|
55
|
+
response = if url_hash.is_a? Hash
|
56
|
+
oauth_connection.request(request_method, "#{path_from_hash(url_hash)}.#{FORMAT}", options)
|
57
|
+
else
|
58
|
+
oauth_connection.request(request_method, url_hash, options)
|
59
|
+
end
|
60
|
+
rescue Exception => ex # rubocop:disable Lint/RescueException
|
61
|
+
msg = ex.message
|
62
|
+
if !msg.nil? && (msg.include?('The access token provided has expired') || msg.include?('expired') || msg.include?('invalid'))
|
63
|
+
@oauth_connection = refreshed_token
|
64
|
+
retry
|
65
|
+
end
|
66
|
+
|
67
|
+
raise Kounta::Errors::RequestError, response.nil? ? 'Unknown Status' : response.status
|
68
|
+
end
|
69
|
+
|
70
|
+
raise Kounta::Errors::RequestError, 'Unknown Status' unless response
|
71
|
+
|
72
|
+
response
|
73
|
+
end
|
74
|
+
|
75
|
+
def objects_from_response(klass, request_method, url_hash, options = {})
|
76
|
+
response = perform(url_hash, request_method, options)
|
77
|
+
last_page = response.headers['x-pages'].to_i - 1
|
78
|
+
results = response.parsed
|
79
|
+
|
80
|
+
# Already got page 0, start at page 1
|
81
|
+
(1..last_page).each do |page_number|
|
82
|
+
response = perform(url_hash, request_method, options.merge!(headers: { 'X-Page' => page_number.to_s }))
|
83
|
+
results += response.parsed
|
84
|
+
end
|
85
|
+
|
86
|
+
results.map { |item| klass.new(item) }
|
87
|
+
end
|
88
|
+
|
89
|
+
def object_from_response(klass, request_method, url_hash, options = {})
|
90
|
+
response = perform(url_hash, request_method, options)
|
91
|
+
klass.new(response.parsed)
|
92
|
+
end
|
93
|
+
|
94
|
+
def objects_from_response_in_time_range(klass, request_method, url_hash, options = {})
|
95
|
+
if options.key?(:params) && (options[:params].key?(:created_gte) ^ options[:params].key?(:created_gt)) &&
|
96
|
+
(options[:params].key?(:created_lte) ^ options[:params].key?(:created_lt))
|
97
|
+
params = options[:params]
|
98
|
+
start_equal = params.key?(:created_gte)
|
99
|
+
finish_equal = params.key?(:created_lte)
|
100
|
+
start = (start_equal ? params[:created_gte] : params[:created_gt]).to_time
|
101
|
+
finish = (finish_equal ? params[:created_lte] : params[:created_lt]).to_time
|
102
|
+
|
103
|
+
params.except!(:created_gte, :created_gt, :created_lte, :created_lt)
|
104
|
+
|
105
|
+
start_date = start.to_date
|
106
|
+
finish_date = finish.to_date
|
107
|
+
|
108
|
+
start = start.to_i
|
109
|
+
finish = finish.to_i
|
110
|
+
|
111
|
+
results = []
|
112
|
+
|
113
|
+
options[:params] = params.merge(created_gte: start_date, created_lte: finish_date)
|
114
|
+
response = perform(url_hash, request_method, options)
|
115
|
+
last_page = response.headers['x-pages'].to_i - 1
|
116
|
+
parsed = response.parsed.map do |o|
|
117
|
+
o['created_at_epoch'] = Time.parse(o['created_at']).to_i
|
118
|
+
o
|
119
|
+
end
|
120
|
+
|
121
|
+
response_start = parsed.last['created_at_epoch']
|
122
|
+
response_finish = parsed.first['created_at_epoch']
|
123
|
+
|
124
|
+
# all current and future responses will be before the required start
|
125
|
+
return results if response_finish < start
|
126
|
+
|
127
|
+
# reverse to order them in ascending order
|
128
|
+
results += filter_responses_in_date_range(parsed.reverse, start, finish)
|
129
|
+
|
130
|
+
# all future responses will be before the required start
|
131
|
+
return results if response_start < start
|
132
|
+
|
133
|
+
(1..last_page).each do |page_number|
|
134
|
+
response = perform(url_hash, request_method, options.merge!(headers: { 'X-Page' => page_number.to_s }))
|
135
|
+
parsed = response.parsed.map do |o|
|
136
|
+
o['created_at_epoch'] = Time.parse(o['created_at']).to_i
|
137
|
+
o
|
138
|
+
end
|
139
|
+
|
140
|
+
response_start = parsed.last['created_at_epoch']
|
141
|
+
response_finish = parsed.first['created_at_epoch']
|
142
|
+
|
143
|
+
# all current and future responses will be before the required start
|
144
|
+
break if response_finish < start
|
145
|
+
|
146
|
+
# reverse to order them in ascending order
|
147
|
+
results += filter_responses_in_date_range(parsed.reverse, start, finish)
|
148
|
+
|
149
|
+
# all future responses will be before the required start
|
150
|
+
break if response_start < start
|
151
|
+
end
|
152
|
+
|
153
|
+
# reverse to under reverses used to order them in ascending order
|
154
|
+
results.reverse.map { |item| klass.new(item) }
|
155
|
+
else
|
156
|
+
raise ArgumentError, 'url_has must contain exactly one of [:created_gte, :created_gt] ' \
|
157
|
+
'and exactly one of [:created_lte, :created_lt]'
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def filter_responses_in_date_range(parsed_responses, start, finish)
|
162
|
+
start_index = parsed_responses.index { |response| response['created_at_epoch'] >= start } || parsed_responses.length - 1
|
163
|
+
finish_index = parsed_responses.rindex { |response| response['created_at_epoch'] <= finish } || 0
|
164
|
+
parsed_responses[start_index..finish_index]
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def oauth_connection
|
170
|
+
@oauth_connection ||= if @refresh_token
|
171
|
+
OAuth2::AccessToken.new(@client, @access_token, refresh_token: @refresh_token).refresh!
|
172
|
+
else
|
173
|
+
OAuth2::AccessToken.new(@client, @access_token)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
def refreshed_token
|
178
|
+
OAuth2::AccessToken.from_hash(@client, refresh_token: @refresh_token).refresh!
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
data/lib/kounta/shift.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
module Kounta
|
2
|
+
class Shift < Kounta::Resource
|
3
|
+
property :company_id
|
4
|
+
property :staff_member
|
5
|
+
property :site
|
6
|
+
property :started_at
|
7
|
+
property :finished_at
|
8
|
+
property :breaks
|
9
|
+
|
10
|
+
# coerce_key :staff_member, Kounta::Staff
|
11
|
+
# coerce_key :site, Kounta::Site
|
12
|
+
coerce_key :breaks, Kounta::Break
|
13
|
+
|
14
|
+
def initialize(hash = {})
|
15
|
+
super(hash)
|
16
|
+
self.breaks ||= []
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_hash
|
20
|
+
returning = {}
|
21
|
+
returning[:breaks] = breaks.map(&:to_hash) if breaks && !breaks.empty?
|
22
|
+
super(returning)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|