workarea-listrak 5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +7 -0
  2. data/.eslintrc.json +36 -0
  3. data/.esvmrc +5 -0
  4. data/.gitignore +32 -0
  5. data/CHANGELOG.md +322 -0
  6. data/Gemfile +14 -0
  7. data/README.md +130 -0
  8. data/Rakefile +53 -0
  9. data/app/assets/javascripts/workarea/admin/modules/listrak/list_events_select.js +83 -0
  10. data/app/assets/javascripts/workarea/storefront/listrak/adapters/listrak_adapter.js +110 -0
  11. data/app/assets/javascripts/workarea/storefront/listrak/listrak_config.js.erb +13 -0
  12. data/app/assets/javascripts/workarea/storefront/listrak/modules/listrak.js +64 -0
  13. data/app/controllers/workarea/admin/listrak/configurations_controller.rb +30 -0
  14. data/app/controllers/workarea/admin/listrak/events_controller.rb +17 -0
  15. data/app/helpers/workarea/listrak/analytics/helper.rb +44 -0
  16. data/app/models/workarea/listrak/configuration.rb +19 -0
  17. data/app/queries/workarea/categorization.decorator +7 -0
  18. data/app/services/workarea/listrak/data_api/customers.rb +26 -0
  19. data/app/services/workarea/listrak/data_api/orders.rb +26 -0
  20. data/app/services/workarea/listrak/data_api/products.rb +26 -0
  21. data/app/services/workarea/listrak/data_api.rb +63 -0
  22. data/app/services/workarea/listrak/email_api/contacts.rb +73 -0
  23. data/app/services/workarea/listrak/email_api/events.rb +44 -0
  24. data/app/services/workarea/listrak/email_api/lists.rb +21 -0
  25. data/app/services/workarea/listrak/email_api/messages.rb +50 -0
  26. data/app/services/workarea/listrak/email_api/transactional_messages.rb +32 -0
  27. data/app/services/workarea/listrak/email_api.rb +71 -0
  28. data/app/services/workarea/listrak/models/address.rb +83 -0
  29. data/app/services/workarea/listrak/models/contact.rb +67 -0
  30. data/app/services/workarea/listrak/models/contact_form.rb +24 -0
  31. data/app/services/workarea/listrak/models/customer_form.rb +209 -0
  32. data/app/services/workarea/listrak/models/event.rb +45 -0
  33. data/app/services/workarea/listrak/models/event_form.rb +25 -0
  34. data/app/services/workarea/listrak/models/list.rb +204 -0
  35. data/app/services/workarea/listrak/models/message_form.rb +147 -0
  36. data/app/services/workarea/listrak/models/order_form.rb +352 -0
  37. data/app/services/workarea/listrak/models/order_item.rb +197 -0
  38. data/app/services/workarea/listrak/models/product_form.rb +369 -0
  39. data/app/services/workarea/listrak/models/transactional_message_form.rb +21 -0
  40. data/app/services/workarea/listrak/oauth.rb +45 -0
  41. data/app/services/workarea/listrak/transactional_message.rb +83 -0
  42. data/app/view_models/workarea/admin/listrak_configuration_view_model.rb +17 -0
  43. data/app/views/workarea/admin/listrak/configurations/edit.html.haml +28 -0
  44. data/app/views/workarea/admin/listrak/events/index.json.jbuilder +4 -0
  45. data/app/views/workarea/admin/shared/_listrak_configuration_link.html.haml +1 -0
  46. data/app/views/workarea/storefront/checkouts/_listrak.html.haml +2 -0
  47. data/app/workers/workarea/listrak/customer_exporter.rb +22 -0
  48. data/app/workers/workarea/listrak/order_exporter.rb +22 -0
  49. data/app/workers/workarea/listrak/product_exporter.rb +50 -0
  50. data/app/workers/workarea/listrak/subscribe_email_signup.rb +34 -0
  51. data/app/workers/workarea/listrak/unsubscribe_email_signup.rb +27 -0
  52. data/bin/rails +18 -0
  53. data/bin/rake +16 -0
  54. data/bin/rspec +16 -0
  55. data/bin/rubocop +16 -0
  56. data/config/initializers/append_points.rb +28 -0
  57. data/config/initializers/configuration.rb +8 -0
  58. data/config/locales/en.yml +17 -0
  59. data/config/routes.rb +10 -0
  60. data/docs/Listrak Technical Integration Guide.docx +0 -0
  61. data/lib/generators/workarea/listrak/message/USAGE +9 -0
  62. data/lib/generators/workarea/listrak/message/message_generator.rb +15 -0
  63. data/lib/generators/workarea/listrak/message/templates/message.rb.erb +24 -0
  64. data/lib/workarea/listrak/analytics.rb +17 -0
  65. data/lib/workarea/listrak/bogus_data_api/customers.rb +9 -0
  66. data/lib/workarea/listrak/bogus_data_api/orders.rb +9 -0
  67. data/lib/workarea/listrak/bogus_data_api/products.rb +9 -0
  68. data/lib/workarea/listrak/bogus_data_api/proxy_client.rb +29 -0
  69. data/lib/workarea/listrak/bogus_data_api.rb +46 -0
  70. data/lib/workarea/listrak/bogus_email_api/contacts.rb +8 -0
  71. data/lib/workarea/listrak/bogus_email_api/events.rb +23 -0
  72. data/lib/workarea/listrak/bogus_email_api/lists.rb +63 -0
  73. data/lib/workarea/listrak/bogus_email_api/proxy_client.rb +29 -0
  74. data/lib/workarea/listrak/bogus_email_api/transactional_messages.rb +9 -0
  75. data/lib/workarea/listrak/bogus_email_api.rb +51 -0
  76. data/lib/workarea/listrak/engine.rb +12 -0
  77. data/lib/workarea/listrak/error.rb +39 -0
  78. data/lib/workarea/listrak/version.rb +5 -0
  79. data/lib/workarea/listrak.rb +57 -0
  80. data/readme/listrak-admin.png +0 -0
  81. data/script/admin_ci +9 -0
  82. data/script/ci +11 -0
  83. data/script/core_ci +9 -0
  84. data/script/plugins_ci +9 -0
  85. data/script/storefront_ci +9 -0
  86. data/test/dummy/Rakefile +6 -0
  87. data/test/dummy/bin/bundle +3 -0
  88. data/test/dummy/bin/rails +4 -0
  89. data/test/dummy/bin/rake +4 -0
  90. data/test/dummy/bin/setup +34 -0
  91. data/test/dummy/bin/update +29 -0
  92. data/test/dummy/config/application.rb +18 -0
  93. data/test/dummy/config/boot.rb +5 -0
  94. data/test/dummy/config/cable.yml +9 -0
  95. data/test/dummy/config/environment.rb +5 -0
  96. data/test/dummy/config/environments/development.rb +54 -0
  97. data/test/dummy/config/environments/production.rb +86 -0
  98. data/test/dummy/config/environments/test.rb +43 -0
  99. data/test/dummy/config/initializers/application_controller_renderer.rb +6 -0
  100. data/test/dummy/config/initializers/assets.rb +11 -0
  101. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  102. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  103. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  104. data/test/dummy/config/initializers/inflections.rb +16 -0
  105. data/test/dummy/config/initializers/mime_types.rb +4 -0
  106. data/test/dummy/config/initializers/new_framework_defaults.rb +21 -0
  107. data/test/dummy/config/initializers/session_store.rb +3 -0
  108. data/test/dummy/config/initializers/workarea.rb +5 -0
  109. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  110. data/test/dummy/config/locales/en.yml +23 -0
  111. data/test/dummy/config/puma.rb +47 -0
  112. data/test/dummy/config/routes.rb +5 -0
  113. data/test/dummy/config/secrets.yml +38 -0
  114. data/test/dummy/config/spring.rb +6 -0
  115. data/test/dummy/config.ru +5 -0
  116. data/test/dummy/db/seeds.rb +2 -0
  117. data/test/dummy/public/404.html +67 -0
  118. data/test/dummy/public/422.html +67 -0
  119. data/test/dummy/public/500.html +66 -0
  120. data/test/dummy/public/apple-touch-icon-precomposed.png +0 -0
  121. data/test/dummy/public/apple-touch-icon.png +0 -0
  122. data/test/dummy/public/favicon.ico +0 -0
  123. data/test/factories/workarea/factories/listrak.rb +15 -0
  124. data/test/helpers/workarea/listrak/analytics/helper_test.rb +38 -0
  125. data/test/integration/workarea/admin/listrak/configuration_integration_test.rb +26 -0
  126. data/test/integration/workarea/admin/listrak/events_integration_test.rb +21 -0
  127. data/test/lib/generators/workarea/listrak/message_generator_test.rb +26 -0
  128. data/test/queries/workarea/categorization_test.decorator +15 -0
  129. data/test/services/workarea/listrak/data_api/customers_test.rb +35 -0
  130. data/test/services/workarea/listrak/data_api/orders_test.rb +24 -0
  131. data/test/services/workarea/listrak/data_api/products_test.rb +27 -0
  132. data/test/services/workarea/listrak/email_api/contacts_test.rb +55 -0
  133. data/test/services/workarea/listrak/email_api/events_test.rb +51 -0
  134. data/test/services/workarea/listrak/email_api/lists_test.rb +45 -0
  135. data/test/services/workarea/listrak/email_api/messages_test.rb +28 -0
  136. data/test/services/workarea/listrak/email_api/transactional_messages_test.rb +50 -0
  137. data/test/services/workarea/listrak/oauth_test.rb +27 -0
  138. data/test/services/workarea/listrak/transactional_message_test.rb +48 -0
  139. data/test/support/workarea/listrak/test_order_confirmation_email.rb +116 -0
  140. data/test/support/workarea/listrak/vcr_config.rb +31 -0
  141. data/test/system/workarea/listrak_system_test.rb +75 -0
  142. data/test/test_helper.rb +16 -0
  143. data/test/vcr_cassettes/listrak/data_api/customers_import-successful.yml +88 -0
  144. data/test/vcr_cassettes/listrak/data_api/orders_import-successful.yml +90 -0
  145. data/test/vcr_cassettes/listrak/data_api/products_import-successful.yml +88 -0
  146. data/test/vcr_cassettes/listrak/email_api/contacts_get-successful.yml +87 -0
  147. data/test/vcr_cassettes/listrak/email_api/contacts_upsert-already_unsubscribed.yml +90 -0
  148. data/test/vcr_cassettes/listrak/email_api/contacts_upsert-successful.yml +87 -0
  149. data/test/vcr_cassettes/listrak/email_api/events_create-invalid_parameter.yml +90 -0
  150. data/test/vcr_cassettes/listrak/email_api/events_create-successful.yml +87 -0
  151. data/test/vcr_cassettes/listrak/email_api/events_get-successful.yml +88 -0
  152. data/test/vcr_cassettes/listrak/email_api/lists_all-successful.yml +88 -0
  153. data/test/vcr_cassettes/listrak/email_api/messages_create-successful.yml +88 -0
  154. data/test/vcr_cassettes/listrak/email_api/transactional_messages_create-successful.yml +89 -0
  155. data/test/vcr_cassettes/listrak/oauth-successful.yml +44 -0
  156. data/test/vcr_cassettes/listrak/oauth-unsuccessful.yml +46 -0
  157. data/test/vcr_cassettes/listrak/system_tests/footer_email_signup-successful.yml +171 -0
  158. data/test/vcr_cassettes/listrak/system_tests/footer_email_signup_overrides_unsubscribe.yml +255 -0
  159. data/test/vcr_cassettes/listrak/system_tests/unsubscribing.yml +593 -0
  160. data/test/workers/workarea/listrak/order_exporter_test.rb +31 -0
  161. data/test/workers/workarea/listrak/product_exporter_test.rb +62 -0
  162. data/workarea-listrak.gemspec +16 -0
  163. metadata +223 -0
@@ -0,0 +1,71 @@
1
+ module Workarea
2
+ module Listrak
3
+ class EmailApi
4
+ def initialize(client_id:, client_secret:, open_timeout: nil, read_timeout: nil)
5
+ @client_id = client_id
6
+ @client_secret = client_secret
7
+ @open_timeout = open_timeout
8
+ @read_timeout = read_timeout
9
+ end
10
+
11
+ def request(http_request)
12
+ token = Listrak::Oauth.token(client_id: client_id, client_secret: client_secret)
13
+
14
+ http_request.add_field 'Authorization', "Bearer #{token}"
15
+ http_request.add_field 'Content-Type', 'application/json'
16
+ http_request['Accept'] = 'application/json'
17
+
18
+ response = http.start do |conn|
19
+ conn.request http_request
20
+ end
21
+
22
+ case response
23
+ when ::Net::HTTPSuccess
24
+ response
25
+ else
26
+ raise Listrak::HttpError, response
27
+ end
28
+ end
29
+
30
+ def events
31
+ Events.new self
32
+ end
33
+
34
+ def contacts
35
+ Contacts.new self
36
+ end
37
+
38
+ def lists
39
+ Lists.new self
40
+ end
41
+
42
+ def messages
43
+ Messages.new self
44
+ end
45
+
46
+ def transactional_messages
47
+ TransactionalMessages.new self
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :client_id, :client_secret, :open_timeout, :read_timeout
53
+
54
+ def http
55
+ Net::HTTP.new(uri.host, uri.port).tap do |conn|
56
+ conn.use_ssl = true
57
+ conn.read_timeout = read_timeout if read_timeout
58
+ conn.open_timeout = open_timeout if open_timeout
59
+ end
60
+ end
61
+
62
+ def base_url
63
+ "https://api.listrak.com/email/"
64
+ end
65
+
66
+ def uri
67
+ URI base_url
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,83 @@
1
+ module Workarea
2
+ module Listrak
3
+ module Models
4
+ class Address
5
+ attr_reader :workarea_address
6
+
7
+ def initialize(workarea_address)
8
+ @workarea_address = workarea_address
9
+ end
10
+
11
+ def as_json
12
+ {
13
+ address1: address1,
14
+ address2: address2,
15
+ address3: address3,
16
+ city: city,
17
+ country: country,
18
+ state: state
19
+ }.compact
20
+ end
21
+
22
+ # Home address line 1
23
+ #
24
+ # limited to 100 characters
25
+ #
26
+ # @return [String]
27
+ #
28
+ def address1
29
+ workarea_address.street.to_s[0...100]
30
+ end
31
+
32
+ # Home address line 2
33
+ #
34
+ # limited to 100 characters
35
+ #
36
+ # @return [String]
37
+ #
38
+ def address2
39
+ workarea_address.street_2.to_s[0..100]
40
+ end
41
+
42
+ # Home address line 3
43
+ #
44
+ # limited to 100 characters
45
+ #
46
+ # @return [String]
47
+ #
48
+ def address3
49
+ end
50
+
51
+ # Home address city
52
+ #
53
+ # limited to 100 characters
54
+ #
55
+ # @return [String]
56
+ #
57
+ def city
58
+ workarea_address.city.to_s[0...100]
59
+ end
60
+
61
+ # Home address country
62
+ #
63
+ # limited to 100 characters
64
+ #
65
+ # @return [String]
66
+ #
67
+ def country
68
+ workarea_address.country.name[0...100]
69
+ end
70
+
71
+ # Home address state
72
+ #
73
+ # limited to 100 characters
74
+ #
75
+ # @return [String]
76
+ #
77
+ def state
78
+ workarea_address.region.to_s[0...100]
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,67 @@
1
+ module Workarea
2
+ module Listrak
3
+ class Models::Contact
4
+ attr_reader :hash
5
+
6
+ def initialize(hash)
7
+ @hash = hash
8
+ end
9
+
10
+ # Email address of the contact.
11
+ #
12
+ # @return [String]
13
+ #
14
+ def email_address
15
+ hash['emailAddress']
16
+ end
17
+
18
+ # Listrak email key of the contact.
19
+ #
20
+ # @return [String]
21
+ #
22
+ def email_key
23
+ hash['emailKey']
24
+ end
25
+
26
+ # Subscription state of the contact. Allowed values are Subscribed and Unsubscribed.
27
+ #
28
+ # @return [String]
29
+ #
30
+ def subscription_state
31
+ hash['subscriptionState']
32
+ end
33
+
34
+ # Subscribe date of the contact.
35
+ #
36
+ # @return [DateTime]
37
+ #
38
+ def subscribe_date
39
+ DateTime.strptime(hash["subscribeDate"], '%FT%T')
40
+ end
41
+
42
+ # Subscribe method of the contact.
43
+ #
44
+ # @return [String]
45
+ #
46
+ def subscribe_method
47
+ hash['subscribeMethod']
48
+ end
49
+
50
+ # Unsubscribe date of the contact.
51
+ #
52
+ # @return [DateTime]
53
+ #
54
+ def unsubscribe_date
55
+ DateTime.strptime(hash["unsubscribeDate"], '%FT%T')
56
+ end
57
+
58
+ # Unsubscribe method of the contact.
59
+ #
60
+ # @return [String]
61
+ #
62
+ def unsubscribe_method
63
+ hash['unsubscribeMethod']
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,24 @@
1
+ module Workarea
2
+ module Listrak
3
+ module Models
4
+ class ContactForm
5
+ SUBSCRIBED_STATES = ['Subscribed', 'Unsubscribed']
6
+ attr_reader :email, :subscription_state, :options
7
+
8
+ def initialize(email:, subscription_state: nil, **options)
9
+ @email = email
10
+ @subscription_state = subscription_state.presence_in SUBSCRIBED_STATES
11
+ @options = options
12
+ end
13
+
14
+ def to_json
15
+ {
16
+ emailAddress: email,
17
+ subscriptionState: subscription_state,
18
+ segmentationFieldValues: nil
19
+ }.to_json
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,209 @@
1
+ module Workarea
2
+ module Listrak
3
+ module Models
4
+ class CustomerForm
5
+ GENDERS = ['M', 'F', 'Male', 'Female']
6
+
7
+ # @param [Workarea::User] user to create a customer
8
+ #
9
+ def initialize(user)
10
+ @user = user
11
+ end
12
+
13
+ def as_json(_options)
14
+ {
15
+ address: address,
16
+ birthday: birthday,
17
+ customerNumber: customer_number,
18
+ email: email,
19
+ firstName: first_name,
20
+ gender: gender,
21
+ homePhone: home_phone,
22
+ lastName: last_name,
23
+ meta1: meta1,
24
+ meta2: meta2,
25
+ meta3: meta3,
26
+ meta4: meta4,
27
+ meta5: meta5,
28
+ mobilePhone: mobile_phone,
29
+ preferredStoreNumber: preferred_store_number,
30
+ registered: registered,
31
+ workPhone: work_phone,
32
+ zipCode: zip_code
33
+ }.compact
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :user
39
+
40
+ # Address of customer
41
+ #
42
+ # @return [Workarea::Listrak::Models::Address]
43
+ #
44
+ def address
45
+ return unless user.default_billing_address.present?
46
+
47
+ Listrak::Models::Address.new(user.default_billing_address)
48
+ end
49
+
50
+ # Customer birthdate
51
+ #
52
+ # @return [String] birthday DateTime in %FT%TZ
53
+ #
54
+ def birthday
55
+ end
56
+
57
+ # Internal customer number
58
+ #
59
+ # limited to 20 characters
60
+ #
61
+ # @return [String]
62
+ #
63
+ def customer_number
64
+ user.id.to_s[0...20]
65
+ end
66
+
67
+ # Email address of customer. This property is required for accounts
68
+ # that are not CRM enabled.
69
+ #
70
+ # limited to 100 characters
71
+ #
72
+ # @return [String]
73
+ #
74
+ def email
75
+ user.email.to_s[0...100]
76
+ end
77
+
78
+ # First name
79
+ #
80
+ # limited to 50 characters
81
+ #
82
+ # @return [String]
83
+ #
84
+ def first_name
85
+ user.first_name
86
+ end
87
+
88
+ # Gender of customer
89
+ #
90
+ # limited to 50 characters
91
+ #
92
+ # @return [String]
93
+ #
94
+ def gender
95
+ end
96
+
97
+ # Landline phone number
98
+ #
99
+ # limted to 50 characters
100
+ #
101
+ # @return [String]
102
+ #
103
+ def home_phone
104
+ user.default_billing_address&.phone_number
105
+ end
106
+
107
+ # Last name
108
+ #
109
+ # limited to 50 characters
110
+ #
111
+ # @return [String]
112
+ #
113
+ def last_name
114
+ user.last_name
115
+ end
116
+
117
+ # Additional meta imformation
118
+ #
119
+ # limted to 250 characters
120
+ #
121
+ # @return [String]
122
+ #
123
+ def meta1
124
+ end
125
+
126
+ # Additional meta imformation
127
+ #
128
+ # limted to 250 characters
129
+ #
130
+ # @return [String]
131
+ #
132
+ def meta2
133
+ end
134
+
135
+ # Additional meta imformation
136
+ #
137
+ # limted to 250 characters
138
+ #
139
+ # @return [String]
140
+ #
141
+ def meta3
142
+ end
143
+
144
+ # Additional meta imformation
145
+ #
146
+ # limted to 250 characters
147
+ #
148
+ # @return [String]
149
+ #
150
+ def meta4
151
+ end
152
+
153
+ # Additional meta imformation
154
+ #
155
+ # limted to 250 characters
156
+ #
157
+ # @return [String]
158
+ #
159
+ def meta5
160
+ end
161
+
162
+ # Mobile phone number
163
+ #
164
+ # limited to 50 characters
165
+ #
166
+ # @return [String]
167
+ def mobile_phone
168
+ user.default_billing_address&.phone_number
169
+ end
170
+
171
+ # Preferred brick and mortar store location
172
+ #
173
+ # limited to 100 characters
174
+ #
175
+ # @return [String]
176
+ #
177
+ def preferred_store_number
178
+ end
179
+
180
+ # Indicates the customer registration state
181
+ #
182
+ # @return [Boolean]
183
+ #
184
+ def registered
185
+ true
186
+ end
187
+
188
+ # Work phone number
189
+ #
190
+ # limited to 50 characters
191
+ #
192
+ # @return [String]
193
+ #
194
+ def work_phone
195
+ end
196
+
197
+ # Customer ZIP Code
198
+ #
199
+ # limited to 50 characters
200
+ #
201
+ # @return [String]
202
+ #
203
+ def zip_code
204
+ user.default_billing_address&.postal_code
205
+ end
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,45 @@
1
+ module Workarea
2
+ module Listrak
3
+ module Models
4
+ class Event
5
+ attr_reader :hash
6
+
7
+ def initialize(hash)
8
+ @hash = hash
9
+ end
10
+
11
+ # Identifier used to locate the event.
12
+ #
13
+ # @return [Integer]
14
+ #
15
+ def id
16
+ hash["eventId"]
17
+ end
18
+
19
+ # Name given to the event.
20
+ #
21
+ # @return [String]
22
+ #
23
+ def name
24
+ hash["eventName"]
25
+ end
26
+
27
+ # Identifier of the event group associated with the event.
28
+ #
29
+ # @return [Integer]
30
+ #
31
+ def group_id
32
+ hash["eventGroupId"]
33
+ end
34
+
35
+ # Status of the event. Allowed values are Active and Blocked.
36
+ #
37
+ # @return [String]
38
+ #
39
+ def status
40
+ hash["status"]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,25 @@
1
+ module Workarea
2
+ module Listrak
3
+ module Models
4
+ class EventForm
5
+ STATUSES = ['Active', 'Blocked']
6
+ attr_reader :name, :status, :group_ip, :options
7
+
8
+ def initialize(name:, status: nil, group_ip: nil, **options)
9
+ @name = name
10
+ @status = status.presence_in(STATUSES) || STATUSES.first
11
+ @group_ip = group_ip
12
+ @options = options
13
+ end
14
+
15
+ def to_json
16
+ {
17
+ eventName: name,
18
+ eventGroupId: group_ip,
19
+ status: status
20
+ }.compact.to_json
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end