printavo-ruby 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a7786dadca364f38068636a9a6e30efac78ac79107c8dfb4ade22b39db5f0bc1
4
- data.tar.gz: f0464978791f12c8e3f5d37d2de09ecfc4b97e57aca4999beda995b68957d2e1
3
+ metadata.gz: 5c8061b68869c47ca86f6c37dc51d60020c035364ea70c68e68bad1df2f4da6c
4
+ data.tar.gz: 1d9c8f4d5805ab82bca17f28891fdc21899f2b166638c4e100e8531c8275073f
5
5
  SHA512:
6
- metadata.gz: a6aae8c15d5f5c348d8e717c09eaf85092a9421d473541ac1ef3c38042534ed1aa14c6e0c5283d6878325cdf32a7325fed52e980913e7b15e8c443ed64a695bb
7
- data.tar.gz: bac95ec4243aa408c495f88d7c4841a308f7c49f509d8b7a0f3a4a161cb9db30418d2ba2c79be67f353c89b68d24cec6f70c1487b64496ea5ef74489fe44acc8
6
+ metadata.gz: 9b39cbc93da67a0b81114fa7209720d944d6ee87cd77763876191ce907a38498cf9c3020548b4f3f23f2a3a675bdd5b7f6ccf80ba937e1c929372e6cfe00761d
7
+ data.tar.gz: ab0e28809466df5269eece7960b2d425c1d4f15db68361c55aa730909bd3662b8ac9f1e256c49218e7cc03febe71c96661d64a2c472bb96bc2aa8c0f8b97ab13
data/README.md CHANGED
@@ -136,6 +136,57 @@ end
136
136
  all_jobs = client.jobs.all_pages(order_id: "99")
137
137
  ```
138
138
 
139
+ ### Mutations
140
+
141
+ #### Customers
142
+
143
+ ```ruby
144
+ # Create
145
+ customer = client.customers.create(
146
+ primary_contact: { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
147
+ company_name: "Acme Shirts"
148
+ )
149
+ puts customer.full_name # => "Jane Smith"
150
+ puts customer.company # => "Acme Shirts"
151
+
152
+ # Update
153
+ customer = client.customers.update("42", company_name: "New Name Inc")
154
+ ```
155
+
156
+ #### Orders
157
+
158
+ ```ruby
159
+ # Create (Printavo creates orders as quotes first)
160
+ order = client.orders.create(
161
+ contact: { id: "456" },
162
+ due_at: "2026-06-01T09:00:00Z",
163
+ customer_due_at: "2026-06-01",
164
+ nickname: "Summer Rush"
165
+ )
166
+
167
+ # Update
168
+ order = client.orders.update("99", nickname: "Rush Job", production_note: "Ships Friday")
169
+
170
+ # Move to a new status
171
+ registry = client.statuses.registry
172
+ order = client.orders.update_status("99", status_id: registry[:in_production].id)
173
+ puts order.status # => "In Production"
174
+ ```
175
+
176
+ #### Inquiries
177
+
178
+ ```ruby
179
+ # Create
180
+ inquiry = client.inquiries.create(
181
+ name: "Bob Johnson",
182
+ email: "bob@example.com",
183
+ request: "100 hoodies, front + back print"
184
+ )
185
+
186
+ # Update
187
+ inquiry = client.inquiries.update("55", nickname: "Hoodies Rush")
188
+ ```
189
+
139
190
  ### Statuses
140
191
 
141
192
  ```ruby
@@ -196,6 +247,42 @@ result = client.graphql.query(
196
247
  )
197
248
  ```
198
249
 
250
+ ### Mutations
251
+
252
+ Use `mutate` for GraphQL write operations:
253
+
254
+ ```ruby
255
+ result = client.graphql.mutate(
256
+ <<~GQL,
257
+ mutation UpdateOrder($id: ID!, $nickname: String!) {
258
+ updateOrder(id: $id, input: { nickname: $nickname }) {
259
+ order { id nickname }
260
+ errors
261
+ }
262
+ }
263
+ GQL
264
+ variables: { id: "99", nickname: "Rush Job" }
265
+ )
266
+ result["updateOrder"]["order"]["nickname"] # => "Rush Job"
267
+ ```
268
+
269
+ ### Raw GraphQL Pagination
270
+
271
+ Paginate any custom query without a resource wrapper using `paginate`.
272
+ The `path:` option is dot-separated and maps to the connection in the response:
273
+
274
+ ```ruby
275
+ client.graphql.paginate(MY_QUERY, path: "orders", first: 50) do |nodes|
276
+ nodes.each { |n| puts n["nickname"] }
277
+ end
278
+
279
+ # Nested connection (e.g. order.lineItems)
280
+ client.graphql.paginate(JOBS_QUERY, path: "order.lineItems",
281
+ variables: { orderId: "99" }) do |nodes|
282
+ nodes.each { |j| puts j["name"] }
283
+ end
284
+ ```
285
+
199
286
  ## Webhooks
200
287
 
201
288
  `Printavo::Webhooks.verify` provides Rack-compatible HMAC-SHA256 signature verification.
@@ -257,12 +344,11 @@ end
257
344
  | 0.1.0 | Auth + Customers + Orders + Jobs + Webhooks (Rack-compatible) ✅ |
258
345
  | 0.2.0 | Status registry + Inquiries ✅ |
259
346
  | 0.3.0 | Pagination helpers (`each_page`, `all_pages`) + `bin/lint` ✅ |
260
- | 0.4.0 | Expanded GraphQL DSL |
261
- | 0.5.0 | Mutations (create/update) |
347
+ | 0.4.0 | Expanded GraphQL DSL (`mutate`, `paginate`) ✅ |
348
+ | 0.5.0 | Mutations (create/update) |
262
349
  | 0.6.0 | Analytics / Reporting queries |
263
350
  | 0.7.0 | Community burn-in / API stabilization |
264
- | 0.8.0 | Pagination abstraction helpers |
265
- | 0.9.0 | Retry/backoff + rate limit awareness |
351
+ | 0.8.0 | Retry/backoff + rate limit awareness |
266
352
  | 1.0.0 | Stable public SDK |
267
353
 
268
354
  **Rules**: `PATCH` = bug fix · `MINOR` = new backward-compatible feature · `MAJOR` = breaking change
data/docs/CHANGELOG.md CHANGED
@@ -8,6 +8,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ## [0.5.0] - 2026-03-30
12
+
13
+ ### Added
14
+ - `Customers#create(primary_contact:, **input)` — `customerCreate` mutation; normalizes `primaryContact` + `companyName` response fields to match the read-side `Customer` model
15
+ - `Customers#update(id, **input)` — `customerUpdate` mutation
16
+ - `Orders#create(**input)` — `quoteCreate` mutation; normalizes `total` → `totalPrice` in response
17
+ - `Orders#update(id, **input)` — `quoteUpdate` mutation
18
+ - `Orders#update_status(id, status_id:)` — `statusUpdate` mutation; handles `OrderUnion (Quote | Invoice)` via inline fragments
19
+ - `Inquiries#create(**input)` — `inquiryCreate` mutation
20
+ - `Inquiries#update(id, **input)` — `inquiryUpdate` mutation
21
+ - All mutation methods accept snake_case keyword args and camelize them for GraphQL input
22
+ - `CREATE_MUTATION` and `UPDATE_MUTATION` constants on `Customers`, `Orders`, `Inquiries`; `UPDATE_STATUS_MUTATION` on `Orders`
23
+ - Private `build_customer` and `build_order` normalizers centralize mutation response mapping
24
+ - Private `camelize_keys` on `Customers` and `Orders` converts `snake_case` → `camelCase` for input variables
25
+ - Factory helpers `fake_customer_mutation_response` and `fake_order_mutation_response` for testing
26
+
27
+ ## [0.4.0] - 2026-03-30
28
+
29
+ ### Added
30
+ - `GraphqlClient#mutate` — semantic method for GraphQL mutations; identical transport to `query` but signals write intent at the call site; lays the foundation for 0.5.0 resource-level mutations
31
+ - `GraphqlClient#paginate` — cursor-following pagination for raw GraphQL queries; accepts a dot-separated `path:` to resolve nested connections (e.g. `"order.lineItems"`); yields each page's `nodes` array
32
+ - `GraphqlClient#execute` (private) — extracted shared POST logic; `query` and `mutate` both delegate to it
33
+ - `GraphqlClient#dig_path` (private) — resolves dot-separated key paths against nested response hashes
34
+ - Roadmap: removed stale Pagination entry from 0.8.0 (shipped in 0.3.0); Retry/backoff renumbered to 0.8.0
35
+
11
36
  ## [0.3.0] - 2026-03-29
12
37
 
13
38
  ### Added
data/docs/FUTURE.md CHANGED
@@ -20,21 +20,6 @@ printavo sync orders --to crm
20
20
 
21
21
  Planned version: `0.7.0`
22
22
 
23
- ### Pagination Abstraction
24
-
25
- A lazy-enumerator-style helper to automatically page through all results:
26
-
27
- ```ruby
28
- client.customers.each_page do |page|
29
- page.each { |c| process(c) }
30
- end
31
-
32
- # Or collect all:
33
- all_orders = client.orders.all_pages
34
- ```
35
-
36
- Planned version: `0.8.0`
37
-
38
23
  ### Retry/Backoff
39
24
 
40
25
  Intelligent rate limit handling with exponential backoff:
@@ -48,7 +33,7 @@ client = Printavo::Client.new(
48
33
  )
49
34
  ```
50
35
 
51
- Planned version: `0.9.0`
36
+ Planned version: `0.8.0`
52
37
 
53
38
  ### Analytics / Reporting Expansion
54
39
 
@@ -7,16 +7,90 @@ module Printavo
7
7
  @connection = connection
8
8
  end
9
9
 
10
+ # Executes a GraphQL query and returns the parsed `data` hash.
11
+ #
12
+ # @param query_string [String] the GraphQL query document
13
+ # @param variables [Hash] optional input variables
14
+ # @return [Hash]
15
+ #
16
+ # @example
17
+ # client.graphql.query("{ customers { nodes { id } } }")
18
+ # client.graphql.query(
19
+ # "query Customer($id: ID!) { customer(id: $id) { id email } }",
20
+ # variables: { id: "42" }
21
+ # )
10
22
  def query(query_string, variables: {})
11
- response = @connection.post('') do |req|
12
- req.body = JSON.generate(query: query_string, variables: variables)
13
- end
23
+ execute(query_string, variables: variables)
24
+ end
14
25
 
15
- handle_response(response)
26
+ # Executes a GraphQL mutation and returns the parsed `data` hash.
27
+ # Semantically equivalent to `query` — both POST to the same endpoint —
28
+ # but distinguishes write intent at the call site.
29
+ #
30
+ # @param mutation_string [String] the GraphQL mutation document
31
+ # @param variables [Hash] optional input variables
32
+ # @return [Hash]
33
+ #
34
+ # @example
35
+ # client.graphql.mutate(
36
+ # <<~GQL,
37
+ # mutation UpdateOrder($id: ID!, $input: OrderInput!) {
38
+ # updateOrder(id: $id, input: $input) {
39
+ # order { id nickname }
40
+ # errors
41
+ # }
42
+ # }
43
+ # GQL
44
+ # variables: { id: "99", input: { nickname: "Rush Job" } }
45
+ # )
46
+ def mutate(mutation_string, variables: {})
47
+ execute(mutation_string, variables: variables)
48
+ end
49
+
50
+ # Iterates all pages of a paginated GraphQL query, yielding each page's
51
+ # nodes array. The query must accept `$first: Int` and `$after: String`
52
+ # variables, and the target connection must expose `nodes` and `pageInfo`.
53
+ #
54
+ # @param query_string [String] the GraphQL query document
55
+ # @param path [String] dot-separated key path to the connection in the response
56
+ # e.g. "orders" or "customer.orders"
57
+ # @param variables [Hash] additional variables merged with `first` and `after`
58
+ # @param first [Integer] page size (default 25)
59
+ # @yieldparam nodes [Array<Hash>] one page of raw node hashes
60
+ #
61
+ # @example
62
+ # client.graphql.paginate(ORDERS_QUERY, path: "orders") do |nodes|
63
+ # nodes.each { |n| puts n["nickname"] }
64
+ # end
65
+ #
66
+ # @example With extra variables
67
+ # client.graphql.paginate(JOBS_QUERY, path: "order.lineItems",
68
+ # variables: { orderId: "99" }, first: 50) do |nodes|
69
+ # nodes.each { |j| puts j["name"] }
70
+ # end
71
+ def paginate(query_string, path:, variables: {}, first: 25)
72
+ after = nil
73
+ loop do
74
+ data = execute(query_string, variables: variables.merge(first: first, after: after))
75
+ conn = dig_path(data, path)
76
+ nodes = conn&.fetch('nodes', []) || []
77
+ yield nodes
78
+ page_info = conn&.fetch('pageInfo', {}) || {}
79
+ break unless page_info['hasNextPage']
80
+
81
+ after = page_info['endCursor']
82
+ end
16
83
  end
17
84
 
18
85
  private
19
86
 
87
+ def execute(document, variables: {})
88
+ response = @connection.post('') do |req|
89
+ req.body = JSON.generate(query: document, variables: variables)
90
+ end
91
+ handle_response(response)
92
+ end
93
+
20
94
  def handle_response(response)
21
95
  body = response.body
22
96
 
@@ -34,5 +108,11 @@ module Printavo
34
108
 
35
109
  body.is_a?(Hash) ? body['data'] : body
36
110
  end
111
+
112
+ # Resolves a dot-separated path against a nested hash.
113
+ # e.g. dig_path(data, "customer.orders") => data["customer"]["orders"]
114
+ def dig_path(data, path)
115
+ path.split('.').reduce(data) { |obj, key| obj.is_a?(Hash) ? obj[key] : nil }
116
+ end
37
117
  end
38
118
  end
@@ -34,6 +34,38 @@ module Printavo
34
34
  }
35
35
  GQL
36
36
 
37
+ CREATE_MUTATION = <<~GQL.freeze
38
+ mutation CustomerCreate($input: CustomerCreateInput!) {
39
+ customerCreate(input: $input) {
40
+ id
41
+ companyName
42
+ primaryContact {
43
+ id
44
+ firstName
45
+ lastName
46
+ email
47
+ phone
48
+ }
49
+ }
50
+ }
51
+ GQL
52
+
53
+ UPDATE_MUTATION = <<~GQL.freeze
54
+ mutation CustomerUpdate($id: ID!, $input: CustomerInput!) {
55
+ customerUpdate(id: $id, input: $input) {
56
+ id
57
+ companyName
58
+ primaryContact {
59
+ id
60
+ firstName
61
+ lastName
62
+ email
63
+ phone
64
+ }
65
+ }
66
+ }
67
+ GQL
68
+
37
69
  def all(first: 25, after: nil)
38
70
  fetch_page(first: first, after: after).records
39
71
  end
@@ -43,6 +75,36 @@ module Printavo
43
75
  Printavo::Customer.new(data['customer'])
44
76
  end
45
77
 
78
+ # Creates a new customer. Requires a +primary_contact+ hash with at least
79
+ # +firstName+ and +email+. Optional keyword arguments map to CustomerCreateInput.
80
+ #
81
+ # @param primary_contact [Hash] contact fields (firstName, lastName, email, phone)
82
+ # @return [Printavo::Customer]
83
+ #
84
+ # @example
85
+ # client.customers.create(
86
+ # primary_contact: { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
87
+ # company_name: "Acme Shirts"
88
+ # )
89
+ def create(primary_contact:, **input)
90
+ variables = { input: camelize_keys(input).merge(primaryContact: primary_contact) }
91
+ data = @graphql.mutate(CREATE_MUTATION, variables: variables)
92
+ build_customer(data['customerCreate'])
93
+ end
94
+
95
+ # Updates an existing customer by ID.
96
+ #
97
+ # @param id [String, Integer]
98
+ # @return [Printavo::Customer]
99
+ #
100
+ # @example
101
+ # client.customers.update("42", company_name: "New Name")
102
+ def update(id, **input)
103
+ data = @graphql.mutate(UPDATE_MUTATION,
104
+ variables: { id: id.to_s, input: camelize_keys(input) })
105
+ build_customer(data['customerUpdate'])
106
+ end
107
+
46
108
  private
47
109
 
48
110
  def fetch_page(first: 25, after: nil, **)
@@ -55,6 +117,29 @@ module Printavo
55
117
  end_cursor: page_info['endCursor']
56
118
  )
57
119
  end
120
+
121
+ # Normalizes a mutation response into the shape Customer.new expects.
122
+ # The mutation returns companyName + nested primaryContact; our model
123
+ # reads company, firstName, lastName, email, phone from the top level.
124
+ def build_customer(attrs)
125
+ return nil unless attrs
126
+
127
+ normalized = attrs.dup
128
+ normalized['company'] ||= attrs['companyName']
129
+ if (contact = attrs['primaryContact'])
130
+ %w[firstName lastName email phone].each do |field|
131
+ normalized[field] ||= contact[field]
132
+ end
133
+ end
134
+ Printavo::Customer.new(normalized)
135
+ end
136
+
137
+ # Converts snake_case Ruby keyword args to camelCase for GraphQL input.
138
+ def camelize_keys(hash)
139
+ hash.transform_keys do |key|
140
+ key.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
141
+ end
142
+ end
58
143
  end
59
144
  end
60
145
  end
@@ -52,6 +52,50 @@ module Printavo
52
52
  }
53
53
  GQL
54
54
 
55
+ CREATE_MUTATION = <<~GQL.freeze
56
+ mutation InquiryCreate($input: InquiryCreateInput!) {
57
+ inquiryCreate(input: $input) {
58
+ id
59
+ nickname
60
+ totalPrice
61
+ status {
62
+ id
63
+ name
64
+ color
65
+ }
66
+ customer {
67
+ id
68
+ firstName
69
+ lastName
70
+ email
71
+ company
72
+ }
73
+ }
74
+ }
75
+ GQL
76
+
77
+ UPDATE_MUTATION = <<~GQL.freeze
78
+ mutation InquiryUpdate($id: ID!, $input: InquiryInput!) {
79
+ inquiryUpdate(id: $id, input: $input) {
80
+ id
81
+ nickname
82
+ totalPrice
83
+ status {
84
+ id
85
+ name
86
+ color
87
+ }
88
+ customer {
89
+ id
90
+ firstName
91
+ lastName
92
+ email
93
+ company
94
+ }
95
+ }
96
+ }
97
+ GQL
98
+
55
99
  def all(first: 25, after: nil)
56
100
  fetch_page(first: first, after: after).records
57
101
  end
@@ -61,6 +105,31 @@ module Printavo
61
105
  Printavo::Inquiry.new(data['inquiry'])
62
106
  end
63
107
 
108
+ # Creates a new inquiry. Requires +name:+ at minimum.
109
+ #
110
+ # @return [Printavo::Inquiry]
111
+ #
112
+ # @example
113
+ # client.inquiries.create(name: "Jane Smith", email: "jane@example.com",
114
+ # request: "100 hoodies, front + back print")
115
+ def create(**input)
116
+ data = @graphql.mutate(CREATE_MUTATION, variables: { input: input })
117
+ Printavo::Inquiry.new(data['inquiryCreate'])
118
+ end
119
+
120
+ # Updates an existing inquiry by ID.
121
+ #
122
+ # @param id [String, Integer]
123
+ # @return [Printavo::Inquiry]
124
+ #
125
+ # @example
126
+ # client.inquiries.update("55", nickname: "Hoodies Rush")
127
+ def update(id, **input)
128
+ data = @graphql.mutate(UPDATE_MUTATION,
129
+ variables: { id: id.to_s, input: input })
130
+ Printavo::Inquiry.new(data['inquiryUpdate'])
131
+ end
132
+
64
133
  private
65
134
 
66
135
  def fetch_page(first: 25, after: nil, **)
@@ -52,6 +52,73 @@ module Printavo
52
52
  }
53
53
  GQL
54
54
 
55
+ # Printavo creates orders as quotes first; the mutation is quoteCreate.
56
+ CREATE_MUTATION = <<~GQL.freeze
57
+ mutation QuoteCreate($input: QuoteCreateInput!) {
58
+ quoteCreate(input: $input) {
59
+ id
60
+ nickname
61
+ total
62
+ status {
63
+ id
64
+ name
65
+ color
66
+ }
67
+ customer {
68
+ id
69
+ firstName
70
+ lastName
71
+ email
72
+ company
73
+ }
74
+ }
75
+ }
76
+ GQL
77
+
78
+ UPDATE_MUTATION = <<~GQL.freeze
79
+ mutation QuoteUpdate($id: ID!, $input: QuoteInput!) {
80
+ quoteUpdate(id: $id, input: $input) {
81
+ id
82
+ nickname
83
+ total
84
+ status {
85
+ id
86
+ name
87
+ color
88
+ }
89
+ customer {
90
+ id
91
+ firstName
92
+ lastName
93
+ email
94
+ company
95
+ }
96
+ }
97
+ }
98
+ GQL
99
+
100
+ # statusUpdate returns an OrderUnion (Quote | Invoice) — requires fragments.
101
+ UPDATE_STATUS_MUTATION = <<~GQL.freeze
102
+ mutation StatusUpdate($parentId: ID!, $statusId: ID!) {
103
+ statusUpdate(parentId: $parentId, statusId: $statusId) {
104
+ ... on Quote {
105
+ id
106
+ nickname
107
+ total
108
+ status { id name color }
109
+ customer { id firstName lastName email company }
110
+ }
111
+ ... on Invoice {
112
+ id
113
+ nickname
114
+ total
115
+ status { id name color }
116
+ customer { id firstName lastName email company }
117
+ }
118
+ }
119
+ }
120
+ GQL
121
+
55
122
  def all(first: 25, after: nil)
56
123
  fetch_page(first: first, after: after).records
57
124
  end
@@ -61,6 +128,50 @@ module Printavo
61
128
  Printavo::Order.new(data['order'])
62
129
  end
63
130
 
131
+ # Creates a new order (quote) in Printavo.
132
+ # Requires at minimum +contact:+ (IDInput), +due_at:+, and +customer_due_at:+.
133
+ #
134
+ # @return [Printavo::Order]
135
+ #
136
+ # @example
137
+ # client.orders.create(
138
+ # contact: { id: "456" },
139
+ # due_at: "2026-06-01T09:00:00Z",
140
+ # customer_due_at: "2026-06-01"
141
+ # )
142
+ def create(**input)
143
+ data = @graphql.mutate(CREATE_MUTATION, variables: { input: camelize_keys(input) })
144
+ build_order(data['quoteCreate'])
145
+ end
146
+
147
+ # Updates an existing order (quote or invoice) by ID.
148
+ #
149
+ # @param id [String, Integer]
150
+ # @return [Printavo::Order]
151
+ #
152
+ # @example
153
+ # client.orders.update("99", nickname: "Rush Job", production_note: "Ships Friday")
154
+ def update(id, **input)
155
+ data = @graphql.mutate(UPDATE_MUTATION,
156
+ variables: { id: id.to_s, input: camelize_keys(input) })
157
+ build_order(data['quoteUpdate'])
158
+ end
159
+
160
+ # Moves an order to a new status.
161
+ #
162
+ # @param id [String, Integer] order (quote/invoice) ID
163
+ # @param status_id [String, Integer] target status ID
164
+ # @return [Printavo::Order]
165
+ #
166
+ # @example
167
+ # registry = client.statuses.registry
168
+ # client.orders.update_status("99", status_id: registry[:in_production].id)
169
+ def update_status(id, status_id:)
170
+ data = @graphql.mutate(UPDATE_STATUS_MUTATION,
171
+ variables: { parentId: id.to_s, statusId: status_id.to_s })
172
+ build_order(data['statusUpdate'])
173
+ end
174
+
64
175
  private
65
176
 
66
177
  def fetch_page(first: 25, after: nil, **)
@@ -73,6 +184,22 @@ module Printavo
73
184
  end_cursor: page_info['endCursor']
74
185
  )
75
186
  end
187
+
188
+ # Normalizes mutation response: quoteCreate/quoteUpdate return `total`
189
+ # rather than `totalPrice`. Map it so Order model accessors work unchanged.
190
+ def build_order(attrs)
191
+ return nil unless attrs
192
+
193
+ normalized = attrs.dup
194
+ normalized['totalPrice'] ||= attrs['total']
195
+ Printavo::Order.new(normalized)
196
+ end
197
+
198
+ def camelize_keys(hash)
199
+ hash.transform_keys do |key|
200
+ key.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
201
+ end
202
+ end
76
203
  end
77
204
  end
78
205
  end
@@ -1,4 +1,4 @@
1
1
  # lib/printavo/version.rb
2
2
  module Printavo
3
- VERSION = '0.3.0'.freeze
3
+ VERSION = '0.5.0'.freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: printavo-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stan Carver II