fixably 0.6.1 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4e8f880a2eb954e1467e74c7dec0c84c2b762b50da015e115319a245c447915d
4
- data.tar.gz: de900d1c1b55c4c2f5a45533c835dd961b4bf01374f9c73f7f8d020c6bf90c77
3
+ metadata.gz: 4adb6e9f822e52cf8dd344055878f56a44df8c8e26ef3b6eb615881b45765641
4
+ data.tar.gz: 6beb3aad60d5eb8ad8c4d53e2c874a7550bfdcea39d1ad10f8ed35cd596e6ad5
5
5
  SHA512:
6
- metadata.gz: 4c4b1b68a5043f35c6da7ef3b6454886e79883f440fb4a9a07e2b27a4e8d0f4fb8254724885165d84f94e9b4cf05e30575619829e9a7c947d10592e400bdf588
7
- data.tar.gz: f01f0d472706283fc568a9e2e04c4e3d44b562b28402c6669291b95d085e950f3956603bd7286166240d5651db103bc06b98ceb5300e5953fa7fb0914b213e4d
6
+ metadata.gz: 256864970168a48fe41fcd1cdb967fe1917df4541b0e70c4f967c5c3bab69e8c80f64c9851d909d93de95cd0052983d200668643440990dc9262560f1ae648c9
7
+ data.tar.gz: ac7a96b0a4d72d62c9cb55329144861a2b683772ca10bd352c50ec9c6d70356a6b554643190d78301441c317964d907d76dba8770002216fbeb5268771e05ed9
data/.gitignore CHANGED
@@ -7,6 +7,9 @@
7
7
  /spec/reports/
8
8
  /tmp/
9
9
 
10
+ # macOS directory metadata
11
+ .DS_Store
12
+
10
13
  # sensitive dotenv config files
11
14
  .env
12
15
 
data/CLAUDE.md ADDED
@@ -0,0 +1,135 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Overview
6
+
7
+ `fixably` is a Ruby gem wrapping the [Fixably API](https://docs.fixably.com) v3.
8
+ It is built on top of Active Resource and monkey patches it heavily so that the
9
+ Fixably API's non-standard conventions (camelCase keys, a single `q` query
10
+ parameter, link expansion, hash-wrapped collections) look like an ordinary
11
+ Rails-style model layer to callers.
12
+
13
+ Ruby >= 3.0. The gem has one runtime dependency: `activeresource` (>= 5, < 7).
14
+
15
+ ## Commands
16
+
17
+ ```sh
18
+ bundle exec rake # default task: rspec + rubocop
19
+ bundle exec rspec # full test suite
20
+ bundle exec rspec spec/fixably/resources/order_spec.rb # single file
21
+ bundle exec rspec spec/fixably/resources/order_spec.rb:42 # single example
22
+ bundle exec rspec --only-failures # uses .rspec_status
23
+ bundle exec rubocop -a # lint with safe autocorrect
24
+ bin/console # IRB with the gem loaded
25
+ ```
26
+
27
+ Mutation testing runs via `bin/mutant` (`bin/mutant run`), which requires the
28
+ `mutant-license` gem from a private source declared in the `Gemfile`. Subjects
29
+ and exclusions live in `.mutant.yml`.
30
+
31
+ Tests never hit the network — specs stub `described_class.connection` and assert
32
+ on the exact path/body that *would* have been sent. `spec/helpers/config.rb`
33
+ sets a dummy api_key/subdomain globally in `spec_helper`, so no `.env` file is
34
+ needed (the README's dotenv instructions are stale).
35
+
36
+ ## Architecture
37
+
38
+ ### Layering
39
+
40
+ `ApplicationResource < ActiveResource::Base` ([lib/fixably/application_resource.rb](lib/fixably/application_resource.rb))
41
+ is the base class every resource inherits from. It composes four mixins, each
42
+ solving one impedance mismatch with the Fixably API:
43
+
44
+ - **`Actions`** ([lib/fixably/actions.rb](lib/fixably/actions.rb)) — overrides
45
+ every Active Resource entry point (`all`, `create`, `find`, `first`, `last`,
46
+ `where`, `save`, `destroy`…) to gate on `ActionPolicy` and to run arguments
47
+ through `ArgumentParameterisation` before delegating to `super`.
48
+ - **`Encoding`** ([lib/fixably/encoding.rb](lib/fixably/encoding.rb)) — on
49
+ write, converts underscored keys back to camelCase, strips read-only
50
+ attributes (`remove_on_encode`) and has_many associations, and nests the
51
+ payload under the parent association name when creating a nested record.
52
+ - **`LoadFromResponse`** ([lib/fixably/load_from_response.rb](lib/fixably/load_from_response.rb))
53
+ — on read, underscores keys, unwraps single-element array responses, turns
54
+ nested collection hashes into `PaginatedCollection`s, and deletes
55
+ href-only "empty" associations so Active Resource doesn't mistake them for
56
+ real records.
57
+ - **`Authorization`** ([lib/fixably/authorization.rb](lib/fixably/authorization.rb))
58
+ — injects the API key into request headers.
59
+
60
+ ### The Active Resource monkey patch
61
+
62
+ [lib/fixably/active_resource/base.rb](lib/fixably/active_resource/base.rb)
63
+ patches `ActiveResource::Base` itself (aliasing the originals):
64
+
65
+ - `instantiate_record` deep-underscores every incoming key.
66
+ - `query_string` collapses all non-pagination filters into Fixably's single
67
+ `q=attribute:value,attribute:value` parameter; `expand`, `limit`, `offset`
68
+ and `page` pass through untouched (`non_query_parameters`).
69
+
70
+ It also installs `Fixably::ActiveResource::PaginatedCollection` as the global
71
+ `collection_parser`. Changing behaviour here affects *all* resources, so this
72
+ file is the first place to look when request/response shapes are wrong.
73
+
74
+ ### URLs are derived from class nesting
75
+
76
+ `ApplicationResource.site_url` builds the endpoint from the class name:
77
+
78
+ - `Fixably::Customer` → `https://<subdomain>.fixably.com/api/v3`
79
+ - `Fixably::Order::Note` → `.../api/v3/orders/:order_id` (the middle name part
80
+ is pluralised and becomes a prefix option)
81
+
82
+ So nesting a class under another resource is what makes it a nested endpoint.
83
+ `CreateHasManyRecord` relies on the same convention: appending to a has_many
84
+ collection (`order.notes << note`) sets `parent_association`, fills in the
85
+ `:<parent>_id` prefix option, and saves — and it refuses records nested more
86
+ than one level deep.
87
+
88
+ ### Permissions are declarative
89
+
90
+ Each resource declares `actions %i[create delete list show update]` — only the
91
+ subset Fixably actually supports. `ActionPolicy`
92
+ ([lib/fixably/action_policy.rb](lib/fixably/action_policy.rb)) raises
93
+ `Fixably::UnsupportedError` for anything not declared. When adding a resource,
94
+ the `actions` list is the contract; the specs' shared examples take it as an
95
+ argument and generate the matching request expectations.
96
+
97
+ ### Link expansion and pagination
98
+
99
+ Fixably returns collections as bare `href` stubs unless asked to expand, so
100
+ `ArgumentParameterisation` always adds `expand=items` for list scopes and
101
+ `ResourceLazyLoader` (behind `Model.includes(:assoc)`) merges extra
102
+ associations into that parameter — `has_one` expands as `assocName`,
103
+ `has_many` as `assocName(items)`.
104
+
105
+ `PaginatedCollection` wraps the `{limit, offset, totalItems, items}` envelope
106
+ and adds `next_page`/`previous_page`/`paginated_each`/`paginated_map`.
107
+ `Actions.last` is non-trivial: it fetches a page, then re-requests with
108
+ `offset: total_items - 1` when the last record isn't in the current page.
109
+
110
+ Array values in `where` become Fixably ranged searches (`[from,to]`), with
111
+ `Date`/`Time` values formatted as `%F` — see
112
+ `ArgumentParameterisation#stringify_array_values`.
113
+
114
+ ## Adding a resource
115
+
116
+ 1. Create `lib/fixably/resources/<name>.rb` subclassing `ApplicationResource`
117
+ (or nest the class inside its parent resource for nested endpoints).
118
+ 2. Declare `actions`, a `schema` block (only attributes Fixably accepts on
119
+ create/update), associations, and any validations.
120
+ 3. Require it from [lib/fixably.rb](lib/fixably.rb).
121
+ 4. Add a spec using the `"a resource"` / `"a nested resource"` shared examples
122
+ in [spec/support/shared_examples/](spec/support/shared_examples/).
123
+ 5. Document it in `docs/<name>.md` and link it from the README's supported
124
+ resources list.
125
+
126
+ ## Conventions
127
+
128
+ - Style is enforced by [.rubocop.yml](.rubocop.yml): double quotes, trailing
129
+ dot for multiline chains, trailing commas in multiline literals, endless
130
+ methods (`def api_version = "v3"`) and numbered block params (`_1`) are
131
+ used liberally, and `# frozen_string_literal: true` heads every file.
132
+ - Bump `Fixably::VERSION` in [lib/fixably/version.rb](lib/fixably/version.rb)
133
+ for releases; built gems are committed under `pkg/`.
134
+ - `webhook_events/` holds captured sample webhook payloads (untracked
135
+ reference data, not used by code or tests).
data/Gemfile CHANGED
@@ -5,8 +5,17 @@ source "https://rubygems.org"
5
5
  # Specify your gem's dependencies in fixably.gemspec
6
6
  gemspec
7
7
 
8
- group :development do
9
- source "https://com:jE6Df8B6dPoghEjlbPTiQzMi34dvbO12@gem.mutant.dev" do
10
- gem "mutant-license"
8
+ # Mutation testing requires a licence, and each developer needs their own
9
+ # token. Rather than committing a token here, supply your own via either
10
+ # bundle config set --global gem.mutant.dev com:YOUR_TOKEN
11
+ # or the equivalent environment variable
12
+ # BUNDLE_GEM__MUTANT__DEV="com:YOUR_TOKEN"
13
+ # Without a token the licence gem is skipped so that `bundle install` still
14
+ # works; `bundle exec mutant` is then unavailable.
15
+ if Bundler.settings["gem.mutant.dev"]
16
+ group :development do
17
+ source "https://gem.mutant.dev" do
18
+ gem "mutant-license"
19
+ end
11
20
  end
12
21
  end
data/Gemfile.lock CHANGED
@@ -1,42 +1,61 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fixably (0.6.0)
4
+ fixably (0.7.0)
5
5
  activeresource (>= 5, < 7)
6
+ logger (~> 1.6)
6
7
 
7
8
  GEM
8
- remote: https://com:jE6Df8B6dPoghEjlbPTiQzMi34dvbO12@gem.mutant.dev/
9
+ remote: https://gem.mutant.dev/
9
10
  specs:
10
11
  mutant-license (0.1.1.1.2753182086109243616824084045621781286745.0)
11
12
 
12
13
  GEM
13
14
  remote: https://rubygems.org/
14
15
  specs:
15
- activemodel (7.0.1)
16
- activesupport (= 7.0.1)
17
- activemodel-serializers-xml (1.0.2)
18
- activemodel (> 5.x)
19
- activesupport (> 5.x)
16
+ activemodel (8.1.3.1)
17
+ activesupport (= 8.1.3.1)
18
+ activemodel-serializers-xml (1.0.3)
19
+ activemodel (>= 5.0.0.a)
20
+ activesupport (>= 5.0.0.a)
20
21
  builder (~> 3.1)
21
- activeresource (6.0.0)
22
- activemodel (>= 6.0)
22
+ activeresource (6.2.0)
23
+ activemodel (>= 7.0)
23
24
  activemodel-serializers-xml (~> 1.0)
24
- activesupport (>= 6.0)
25
- activesupport (7.0.1)
26
- concurrent-ruby (~> 1.0, >= 1.0.2)
25
+ activesupport (>= 7.0)
26
+ activesupport (8.1.3.1)
27
+ base64
28
+ bigdecimal
29
+ concurrent-ruby (~> 1.0, >= 1.3.1)
30
+ connection_pool (>= 2.2.5)
31
+ drb
27
32
  i18n (>= 1.6, < 2)
33
+ json
34
+ logger (>= 1.4.2)
28
35
  minitest (>= 5.1)
29
- tzinfo (~> 2.0)
36
+ securerandom (>= 0.3)
37
+ tzinfo (~> 2.0, >= 2.0.5)
38
+ uri (>= 0.13.1)
30
39
  ast (2.4.2)
31
- builder (3.2.4)
32
- byebug (11.1.3)
40
+ base64 (0.3.0)
41
+ bigdecimal (4.1.2)
42
+ builder (3.3.0)
43
+ byebug (13.0.0)
44
+ reline (>= 0.6.0)
33
45
  coderay (1.1.3)
34
- concurrent-ruby (1.1.9)
46
+ concurrent-ruby (1.3.8)
47
+ connection_pool (3.0.2)
35
48
  diff-lcs (1.5.0)
36
- i18n (1.8.11)
49
+ drb (2.2.3)
50
+ i18n (1.15.2)
37
51
  concurrent-ruby (~> 1.0)
38
- method_source (1.0.0)
39
- minitest (5.15.0)
52
+ io-console (0.8.2)
53
+ json (2.21.2)
54
+ logger (1.7.0)
55
+ method_source (1.1.0)
56
+ minitest (6.0.6)
57
+ drb (~> 2.0)
58
+ prism (~> 1.5)
40
59
  mutant (0.11.2)
41
60
  diff-lcs (~> 1.3)
42
61
  parser (~> 3.0.0)
@@ -49,15 +68,19 @@ GEM
49
68
  parallel (1.21.0)
50
69
  parser (3.0.3.2)
51
70
  ast (~> 2.4.1)
52
- pry (0.13.1)
71
+ prism (1.9.0)
72
+ pry (0.16.0)
53
73
  coderay (~> 1.1)
54
74
  method_source (~> 1.0)
55
- pry-byebug (3.9.0)
56
- byebug (~> 11.0)
57
- pry (~> 0.13.0)
75
+ reline (>= 0.6.0)
76
+ pry-byebug (3.12.0)
77
+ byebug (~> 13.0)
78
+ pry (>= 0.13, < 0.17)
58
79
  rainbow (3.1.1)
59
- rake (13.0.6)
80
+ rake (13.4.2)
60
81
  regexp_parser (2.2.0)
82
+ reline (0.7.0)
83
+ io-console (~> 0.5)
61
84
  rexml (3.2.5)
62
85
  rspec (3.10.0)
63
86
  rspec-core (~> 3.10.0)
@@ -91,16 +114,20 @@ GEM
91
114
  rubocop-rspec (2.7.0)
92
115
  rubocop (~> 1.19)
93
116
  ruby-progressbar (1.11.0)
117
+ securerandom (0.4.1)
94
118
  sorbet-runtime (0.5.9531)
95
- tzinfo (2.0.4)
119
+ tzinfo (2.0.6)
96
120
  concurrent-ruby (~> 1.0)
97
121
  unicode-display_width (2.1.0)
98
122
  unparser (0.6.2)
99
123
  diff-lcs (~> 1.3)
100
124
  parser (>= 3.0.0)
125
+ uri (1.1.1)
101
126
 
102
127
  PLATFORMS
103
128
  arm64-darwin-21
129
+ arm64-darwin-22
130
+ arm64-darwin-25
104
131
  x86_64-darwin-20
105
132
 
106
133
  DEPENDENCIES
@@ -116,4 +143,4 @@ DEPENDENCIES
116
143
  rubocop-rspec
117
144
 
118
145
  BUNDLED WITH
119
- 2.2.22
146
+ 4.0.16
data/README.md CHANGED
@@ -217,6 +217,26 @@ replace the example values:
217
217
  cp .env.example .env
218
218
  ```
219
219
 
220
+ ### Mutation testing
221
+
222
+ Mutation testing uses [mutant](https://github.com/mbj/mutant), which requires a
223
+ licence. Since each developer needs their own token, no token is committed to
224
+ this repository; supply yours via Bundler's credential config:
225
+
226
+ ```sh
227
+ bundle config set --global gem.mutant.dev com:YOUR_TOKEN
228
+ ```
229
+
230
+ or, more conveniently in CI, the equivalent environment variable:
231
+
232
+ ```sh
233
+ BUNDLE_GEM__MUTANT__DEV="com:YOUR_TOKEN"
234
+ ```
235
+
236
+ If no token is configured, the licence gem is skipped so that `bundle install`
237
+ still succeeds. Only `bundle exec mutant` is unavailable; `rake` and the RSpec
238
+ suite are unaffected.
239
+
220
240
  ## License
221
241
 
222
242
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/fixably.gemspec CHANGED
@@ -27,6 +27,8 @@ Gem::Specification.new do |spec|
27
27
  spec.require_paths = ["lib"]
28
28
 
29
29
  spec.add_dependency "activeresource", ">= 5", "< 7"
30
+ # Removed from the default gems in Ruby 4.0
31
+ spec.add_dependency "logger", "~> 1.6"
30
32
 
31
33
  spec.add_development_dependency "mutant-rspec", "~> 0.10"
32
34
  spec.add_development_dependency "pry-byebug"
@@ -24,9 +24,13 @@ module Fixably
24
24
  @actions = format_actions(values).freeze
25
25
  end
26
26
 
27
+ # Active Resource 6.1 made ::all lazy, returning a relation rather than
28
+ # performing the request. We call ::find directly so that a
29
+ # PaginatedCollection is always returned, regardless of the Active
30
+ # Resource version.
27
31
  def all(*arguments)
28
32
  ActionPolicy.new(resource: self).list!
29
- super(*arguments)
33
+ find(:all, *arguments)
30
34
  end
31
35
 
32
36
  def create(attributes = {})
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Fixably
4
4
  class Order < ApplicationResource
5
- actions %i[create list show]
5
+ actions %i[create list show update]
6
6
 
7
7
  schema do
8
8
  string :internal_location
@@ -64,7 +64,7 @@ module Fixably
64
64
  class Note < ApplicationResource
65
65
  actions %i[create list show]
66
66
 
67
- ALLOWED_TYPES = %w[DIAGNOSIS INTERNAL ISSUE RESOLUTION].freeze
67
+ ALLOWED_TYPES = %w[DIAGNOSIS DETAILS INTERNAL ISSUE RESOLUTION].freeze
68
68
 
69
69
  validates(
70
70
  :type,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fixably
4
- VERSION = "0.6.1"
4
+ VERSION = "0.7.0"
5
5
  end
data/lib/fixably.rb CHANGED
@@ -35,4 +35,6 @@ module Fixably
35
35
  config.logger
36
36
  end
37
37
  end
38
+
39
+ ActiveSupport.run_load_hooks(:fixably, self) if defined?(ActiveSupport)
38
40
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fixably
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adam Rice
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2022-01-19 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activeresource
@@ -30,6 +29,20 @@ dependencies:
30
29
  - - "<"
31
30
  - !ruby/object:Gem::Version
32
31
  version: '7'
32
+ - !ruby/object:Gem::Dependency
33
+ name: logger
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.6'
39
+ type: :runtime
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - "~>"
44
+ - !ruby/object:Gem::Version
45
+ version: '1.6'
33
46
  - !ruby/object:Gem::Dependency
34
47
  name: mutant-rspec
35
48
  requirement: !ruby/object:Gem::Requirement
@@ -142,7 +155,6 @@ dependencies:
142
155
  - - ">="
143
156
  - !ruby/object:Gem::Version
144
157
  version: '0'
145
- description:
146
158
  email:
147
159
  - development@hashnotadam.com
148
160
  executables: []
@@ -154,6 +166,7 @@ files:
154
166
  - ".mutant.yml"
155
167
  - ".rspec"
156
168
  - ".rubocop.yml"
169
+ - CLAUDE.md
157
170
  - CODE_OF_CONDUCT.md
158
171
  - Gemfile
159
172
  - Gemfile.lock
@@ -204,7 +217,6 @@ metadata:
204
217
  rubygems_mfa_required: 'true'
205
218
  homepage_uri: https://github.com/HashNotAdam/fixably-ruby
206
219
  source_code_uri: https://github.com/HashNotAdam/fixably-ruby
207
- post_install_message:
208
220
  rdoc_options: []
209
221
  require_paths:
210
222
  - lib
@@ -219,8 +231,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
219
231
  - !ruby/object:Gem::Version
220
232
  version: '0'
221
233
  requirements: []
222
- rubygems_version: 3.3.3
223
- signing_key:
234
+ rubygems_version: 4.0.16
224
235
  specification_version: 4
225
236
  summary: Ruby client for the Fixably API
226
237
  test_files: []