diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..743e86e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,35 @@ +# Changelog + +## [Unreleased] + +### Added + +- **`conversions` configuration** with independent `query_parameters` and `json_attributes` sub-keys. This allows APIs that use different naming conventions for query parameters vs JSON body attributes to be configured correctly: + + ```ruby + module MyAPI + extend RestEasy + + configure do + conversions.json_attributes = :camelCase + conversions.query_parameters = :PascalCase + end + end + ``` + +- **Automatic query parameter key transformation.** `Resource.get` now transforms parameter keys according to the `query_parameters` convention before sending the request. This removes the need for manual `transform_keys` calls in consuming gems. + +- `conversions` can be overridden per Resource class, with inheritance falling back to the parent API module configuration. + +### Deprecated + +- **`attribute_convention`** is deprecated in favour of `conversions.json_attributes`. The old setting continues to work — it is propagated to `conversions.json_attributes` at the module level and respected as a fallback at the resource level — but emits a deprecation warning in both cases. + +### Removed + +- **`dry-inflector` runtime dependency.** The gem never used `Dry::Inflector` — Zeitwerk's own inflector is the only one used. +- **Default value for `attribute_convention`.** Previously defaulted to `:PascalCase`. The setting is now unset by default; reading `MyAPI::Settings.config.attribute_convention` directly returns `nil` unless explicitly configured. The effective default for naming conversion now lives on `conversions.json_attributes` (also `:PascalCase`). + +## [1.0.0] + +Initial release. diff --git a/Gemfile.lock b/Gemfile.lock index 7ced8de..766f391 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,9 +1,8 @@ PATH remote: . specs: - rest-easy (0.1.0) + rest-easy (1.0.0) dry-configurable (~> 0.14) - dry-inflector (~> 0.2.1) dry-types (~> 1.2) faraday (~> 2.0) zeitwerk (~> 2.6) diff --git a/README.md b/README.md index 244f80b..5909053 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ module Acme configure do base_url "https://api.acme.com/v1" authentication RestEasy::Auth::PSK.new(api_key: ENV["ACME_API_KEY"]) - attribute_convention :PascalCase end end @@ -87,19 +86,19 @@ module Fortnox base_url "https://api.fortnox.se/3" max_retries 3 authentication RestEasy::Auth::PSK.new(api_key: ENV["FORTNOX_KEY"]) - attribute_convention :PascalCase end end ``` ### Available settings -| Setting | Default | Description | -|------------------------|----------------------------|------------------------------------------| -| `base_url` | `"https://example.com"` | Base URL for all requests | -| `max_retries` | `3` | Retry count on request failure | -| `authentication` | `Auth::Null.new` | Authentication strategy | -| `attribute_convention` | `:PascalCase` | Naming convention for API field mapping | +| Setting | Default | Description | +|----------------------------------|----------------------------|---------------------------------------------------| +| `base_url` | `"https://example.com"` | Base URL for all requests | +| `max_retries` | `3` | Retry count on request failure | +| `authentication` | `Auth::Null.new` | Authentication strategy | +| `conversions.json_attributes` | `:PascalCase` | Naming convention for JSON response/request fields| +| `conversions.query_parameters` | `:PascalCase` | Naming convention for query parameter keys | ### Faraday middleware @@ -191,7 +190,7 @@ The full `Dry::Types` vocabulary is available inside resource bodies — `Strict ### Naming conventions -RestEasy automatically maps between Ruby's `snake_case` attribute names and the API's naming convention: +RestEasy automatically maps between Ruby's `snake_case` attribute names and the API's naming convention. The `conversions` config controls this independently for JSON attributes and query parameters. Both default to `:PascalCase`: | Convention | Ruby attr | API field | |---------------|--------------------|----------------------| @@ -199,12 +198,27 @@ RestEasy automatically maps between Ruby's `snake_case` attribute names and the | `:camelCase` | `:document_number` | `"documentNumber"` | | `:snake_case` | `:document_number` | `"document_number"` | -Set the convention at the module level (applies to all resources) or override per resource: +Set conventions at the module level (applies to all resources): ```ruby -attribute_convention :camelCase +configure do + conversions.json_attributes = :camelCase + conversions.query_parameters = :PascalCase +end ``` +Or override per resource: + +```ruby +class MyAPI::Special < MyAPI::Resource + configure do + conversions.json_attributes = :PascalCase + end +end +``` + +Query parameter keys are automatically transformed when calling `get` with `params:`. For example, with `query_parameters: :PascalCase`, `params: { sort_order: "asc" }` becomes `?SortOrder=asc` in the request. + You can also provide a custom convention object with `parse(api_name)` and `serialise(model_name)` methods. ### Explicit name mapping @@ -737,7 +751,7 @@ module MyAPI base_url "https://api.example.com/v1" max_retries 3 authentication RestEasy::Auth::PSK.new(api_key: ENV["MY_API_KEY"]) - attribute_convention :PascalCase + conversions.json_attributes = :PascalCase end end ``` diff --git a/lib/rest_easy.rb b/lib/rest_easy.rb index 8f19cf4..16a179c 100644 --- a/lib/rest_easy.rb +++ b/lib/rest_easy.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "rubygems" -require "dry/inflector" require "dry/types" require "faraday" require "zeitwerk" @@ -83,6 +82,17 @@ def configure(&block) else yield self::Settings.config end + + # Backwards compatibility: propagate the deprecated attribute_convention + # to conversions, but only on changes — so repeated `configure` calls + # don't re-warn and don't clobber a `conversions.json_attributes` set in + # a later call. + ac = self::Settings.config.attribute_convention + if ac && @_propagated_attribute_convention != ac + warn "RestEasy: attribute_convention is deprecated, use `conversions.json_attributes = #{ac.inspect}` instead" + self::Settings.config.conversions.json_attributes = ac + @_propagated_attribute_convention = ac + end end end diff --git a/lib/rest_easy/conventions.rb b/lib/rest_easy/conventions.rb index f0b3307..09afef6 100644 --- a/lib/rest_easy/conventions.rb +++ b/lib/rest_easy/conventions.rb @@ -56,6 +56,8 @@ def serialise(model_name) snake_case: SnakeCase.new }.freeze + DEFAULT = :PascalCase + def self.resolve(convention) case convention when Symbol diff --git a/lib/rest_easy/resource.rb b/lib/rest_easy/resource.rb index 8a759db..9d87d59 100644 --- a/lib/rest_easy/resource.rb +++ b/lib/rest_easy/resource.rb @@ -9,6 +9,11 @@ class Resource setting :path setting :debug, default: false + setting :conversions do + setting :query_parameters # nil default — falls back to parent module + setting :json_attributes # nil default — falls back to parent module + end + # ── Types ───────────────────────────────────────────────────────────── # Include Types so the full Dry::Types vocabulary (Strict::String, # Coercible::Integer, Params::Date, etc.) is available without prefix. @@ -143,16 +148,32 @@ def metadata(**kwargs) end end - # -- attribute_convention ------------------------------------------ + # -- conversions --------------------------------------------------- + + def json_attribute_converter + Conventions.resolve( + config.conversions.json_attributes || + parent&.config&.conversions&.json_attributes || + Conventions::DEFAULT + ) + end + + def query_parameter_converter + Conventions.resolve( + config.conversions.query_parameters || + parent&.config&.conversions&.query_parameters || + Conventions::DEFAULT + ) + end + + # -- attribute_convention (deprecated) ------------------------------- def attribute_convention(value = nil) if value - @attribute_convention = Conventions.resolve(value) - else - @attribute_convention || - (superclass.respond_to?(:attribute_convention) ? superclass.attribute_convention : nil) || - Conventions.resolve(parent&.config&.attribute_convention || :PascalCase) + warn "RestEasy: attribute_convention is deprecated, use `configure { conversions.json_attributes = #{value.inspect} }` instead" + config.conversions.json_attributes = value end + json_attribute_converter end private @@ -191,7 +212,7 @@ def attr(name_or_mapping, *args, &block) attribute_api_name = name_or_mapping[1].to_s else attribute_model_name = name_or_mapping.to_sym - attribute_api_name = attribute_convention.serialise(attribute_model_name) + attribute_api_name = json_attribute_converter.serialise(attribute_model_name) end # Extract type (non-Symbol), flags (Symbols), and optional mapper object @@ -459,7 +480,8 @@ def delete(id) # HTTP primitives — delegate to the parent API module's connection def get(path:, params: {}, headers: {}) - parent.get(path:, params:, headers:) + converted_params = params.transform_keys { |k| query_parameter_converter.serialise(k) } + parent.get(path:, params: converted_params, headers:) end def post(path:, body: nil, headers: {}) @@ -578,7 +600,7 @@ def serialise serialised = attr_def.serialise_value(value) if serialised.is_a?(::Array) # Array return: zip with source field API names - convention = klass.attribute_convention + convention = klass.json_attribute_converter attr_def.source_fields.zip(serialised).each do |field_name, field_value| api_key = convention.serialise(field_name) result[api_key] = field_value @@ -655,7 +677,7 @@ def init_from_api(api_data, extra_meta = {}) if attr_def.source_fields.any? # Source fields declared via block params: extract individual # values from api_data using convention, splat into parse block. - convention = klass.attribute_convention + convention = klass.json_attribute_converter raw_values = attr_def.source_fields.map do |field_name| api_key = convention.serialise(field_name) api_data[api_key] @@ -678,7 +700,7 @@ def init_from_api(api_data, extra_meta = {}) if config.debug # Warn about API fields that are neither declared attrs nor explicitly ignored - convention = klass.attribute_convention + convention = klass.json_attribute_converter known_api_keys = klass.all_attribute_definitions.values.flat_map do |ad| keys = [ad.api_name] ad.source_fields.each { |sf| keys << convention.serialise(sf) } diff --git a/lib/rest_easy/settings.rb b/lib/rest_easy/settings.rb index 56d55ea..941f62e 100644 --- a/lib/rest_easy/settings.rb +++ b/lib/rest_easy/settings.rb @@ -9,6 +9,11 @@ class Settings setting :base_url, default: "https://example.com", reader: true setting :max_retries, default: 3, reader: true setting :authentication, default: Auth::Null.new, reader: true - setting :attribute_convention, default: :PascalCase, reader: true + setting :attribute_convention # deprecated — propagated to conversions.json_attributes in configure + + setting :conversions do + setting :query_parameters, default: Conventions::DEFAULT, reader: true + setting :json_attributes, default: Conventions::DEFAULT, reader: true + end end end diff --git a/rest-easy.gemspec b/rest-easy.gemspec index a4c8952..0be08bb 100644 --- a/rest-easy.gemspec +++ b/rest-easy.gemspec @@ -24,7 +24,6 @@ Gem::Specification.new do |spec| spec.add_runtime_dependency "dry-types", "~> 1.2" spec.add_dependency "zeitwerk", "~> 2.6" - spec.add_dependency "dry-inflector", "~> 0.2.1" spec.add_dependency "dry-configurable", "~> 0.14" spec.add_dependency "faraday", "~> 2.0" diff --git a/spec/rest_easy/resource/conversions_spec.rb b/spec/rest_easy/resource/conversions_spec.rb new file mode 100644 index 0000000..ddf379b --- /dev/null +++ b/spec/rest_easy/resource/conversions_spec.rb @@ -0,0 +1,431 @@ +# frozen_string_literal: true + +RSpec.describe "Resource conversions" do + # Helper to set up a Faraday test adapter with stubs + def setup_test_connection(api_module, &block) + stubs = Faraday::Adapter::Test::Stubs.new(&block) + api_module.instance_variable_set(:@faraday_connection, nil) + api_module.connection do |f| + f.request :json + f.response :json, content_type: /\bjson$/ + f.adapter :test, stubs + end + stubs + end + + # ── Module-level configuration ────────────────────────────────────── + + describe "module-level conversions" do + before(:all) do + module ConvTestApi + extend RestEasy + + configure do + conversions.json_attributes = :PascalCase + conversions.query_parameters = :camelCase + end + end + + class ConvTestApi::Invoice < RestEasy::Resource + configure { path "invoices" } + + key :document_number, Integer, :read_only + attr :customer_name, String + end + end + + after(:all) do + Object.send(:remove_const, :ConvTestApi) + end + + it "resolves json_attributes from module config" do + expect(ConvTestApi::Invoice.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "resolves query_parameters from module config" do + expect(ConvTestApi::Invoice.query_parameter_converter).to be_a(RestEasy::Conventions::CamelCase) + end + + it "parses API data using json_attributes convention" do + instance = ConvTestApi::Invoice.parse({ + "DocumentNumber" => 1, + "CustomerName" => "Acme" + }) + + expect(instance.document_number).to eq(1) + expect(instance.customer_name).to eq("Acme") + end + + it "serialises using json_attributes convention" do + instance = ConvTestApi::Invoice.parse({ + "DocumentNumber" => 1, + "CustomerName" => "Acme" + }) + serialised = instance.serialise + + # DocumentNumber is :read_only, so excluded from serialise + expect(serialised).to have_key("CustomerName") + expect(serialised["CustomerName"]).to eq("Acme") + end + + it "transforms query parameter keys using query_parameters convention" do + captured_params = nil + + setup_test_connection(ConvTestApi) do |stub| + stub.get("/invoices") do |env| + captured_params = env.params + [200, { "Content-Type" => "application/json" }, + '[{"DocumentNumber": 1, "CustomerName": "Test"}]'] + end + end + + ConvTestApi::Invoice.get( + path: "invoices", + params: { customer_name: "Test", sort_order: "asc" } + ) + + expect(captured_params).to include("customerName" => "Test", "sortOrder" => "asc") + end + + it "does not mutate the caller-provided params hash" do + setup_test_connection(ConvTestApi) do |stub| + stub.get("/invoices") do + [200, { "Content-Type" => "application/json" }, "[]"] + end + end + + params = { customer_name: "Test" } + ConvTestApi::Invoice.get(path: "invoices", params: params) + + expect(params).to eq(customer_name: "Test") + end + + it "accepts a frozen params hash without raising" do + setup_test_connection(ConvTestApi) do |stub| + stub.get("/invoices") do + [200, { "Content-Type" => "application/json" }, "[]"] + end + end + + params = { customer_name: "Test" }.freeze + + expect { + ConvTestApi::Invoice.get(path: "invoices", params: params) + }.not_to raise_error + end + end + + # ── Resource-level override ───────────────────────────────────────── + + describe "resource-level override" do + before(:all) do + module ResOverrideApi + extend RestEasy + + configure do + conversions.json_attributes = :camelCase + conversions.query_parameters = :camelCase + end + end + + class ResOverrideApi::Base < RestEasy::Resource + end + + class ResOverrideApi::Standard < ResOverrideApi::Base + attr :item_name, String + end + + class ResOverrideApi::Custom < ResOverrideApi::Base + configure do + conversions.json_attributes = :PascalCase + conversions.query_parameters = :PascalCase + end + + attr :item_name, String + end + end + + after(:all) do + Object.send(:remove_const, :ResOverrideApi) + end + + it "inherits module-level convention when not overridden" do + expect(ResOverrideApi::Standard.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + expect(ResOverrideApi::Standard.query_parameter_converter).to be_a(RestEasy::Conventions::CamelCase) + end + + it "uses resource-level convention when overridden" do + expect(ResOverrideApi::Custom.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + expect(ResOverrideApi::Custom.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "parses with inherited convention" do + instance = ResOverrideApi::Standard.parse({ "itemName" => "Widget" }) + expect(instance.item_name).to eq("Widget") + end + + it "parses with overridden convention" do + instance = ResOverrideApi::Custom.parse({ "ItemName" => "Widget" }) + expect(instance.item_name).to eq("Widget") + end + + it "does not affect sibling resources" do + expect(ResOverrideApi::Standard.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + expect(ResOverrideApi::Custom.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + end + end + + # ── Partial override (one key only) ───────────────────────────────── + + describe "partial override" do + before(:all) do + module PartialApi + extend RestEasy + + configure do + conversions.json_attributes = :camelCase + conversions.query_parameters = :camelCase + end + end + + class PartialApi::Resource < RestEasy::Resource + configure do + conversions.query_parameters = :PascalCase + # json_attributes not set — inherits from module + end + + attr :item_name, String + end + end + + after(:all) do + Object.send(:remove_const, :PartialApi) + end + + it "uses overridden query_parameters" do + expect(PartialApi::Resource.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "inherits json_attributes from module" do + expect(PartialApi::Resource.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + end + end + + # ── Independent conventions ───────────────────────────────────────── + + describe "independent query_parameters and json_attributes" do + before(:all) do + module MixedApi + extend RestEasy + + configure do + conversions.json_attributes = :camelCase + conversions.query_parameters = :PascalCase + end + end + + class MixedApi::Item < RestEasy::Resource + configure { path "items" } + attr :item_name, String + end + end + + after(:all) do + Object.send(:remove_const, :MixedApi) + end + + it "uses different conventions for attributes and parameters" do + expect(MixedApi::Item.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + expect(MixedApi::Item.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "serialises attributes as camelCase" do + instance = MixedApi::Item.parse({ "itemName" => "Widget" }) + serialised = instance.serialise + expect(serialised).to have_key("itemName") + end + + it "transforms query params as PascalCase" do + captured_params = nil + + setup_test_connection(MixedApi) do |stub| + stub.get("/items") do |env| + captured_params = env.params + [200, { "Content-Type" => "application/json" }, + '[{"itemName": "Widget"}]'] + end + end + + MixedApi::Item.get(path: "items", params: { item_name: "Widget" }) + + expect(captured_params).to include("ItemName" => "Widget") + end + end + + # ── Backwards compatibility ───────────────────────────────────────── + + describe "backwards compatibility" do + describe "module-level attribute_convention" do + before(:all) do + module BCModuleApi + extend RestEasy + + configure do |config| + config.attribute_convention = :PascalCase + end + end + + class BCModuleApi::Invoice < RestEasy::Resource + attr :customer_name, String + end + end + + after(:all) do + Object.send(:remove_const, :BCModuleApi) + end + + it "propagates to conversions.json_attributes" do + expect(BCModuleApi::Invoice.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "defaults query_parameters to PascalCase" do + expect(BCModuleApi::Invoice.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "parses with the propagated convention" do + instance = BCModuleApi::Invoice.parse({ "CustomerName" => "Acme" }) + expect(instance.customer_name).to eq("Acme") + end + end + + describe "module-level attribute_convention propagation" do + it "warns only once across repeated configure calls" do + module BCRepeatApi + extend RestEasy + end + + original_stderr = $stderr + $stderr = StringIO.new + output = nil + begin + BCRepeatApi.configure { |c| c.attribute_convention = :PascalCase } + BCRepeatApi.configure { |c| c.base_url = "https://api.example.com" } + ensure + output = $stderr.string + $stderr = original_stderr + Object.send(:remove_const, :BCRepeatApi) + end + + expect(output.scan(/attribute_convention is deprecated/).length).to eq(1) + end + + it "does not overwrite a conversions.json_attributes set after attribute_convention" do + module BCOverrideApi + extend RestEasy + end + + original_stderr = $stderr + $stderr = StringIO.new + begin + BCOverrideApi.configure { |c| c.attribute_convention = :PascalCase } + BCOverrideApi.configure { conversions.json_attributes = :camelCase } + ensure + $stderr = original_stderr + end + + expect(BCOverrideApi::Settings.config.conversions.json_attributes).to eq(:camelCase) + Object.send(:remove_const, :BCOverrideApi) + end + end + + describe "resource-level attribute_convention" do + it "sets json_attributes and emits a deprecation warning" do + resource_class = Class.new(RestEasy::Resource) + + expect { + resource_class.attribute_convention :camelCase + }.to output(/deprecated/).to_stderr + + expect(resource_class.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + end + + it "still works as a getter" do + resource_class = Class.new(RestEasy::Resource) do + configure { conversions.json_attributes = :PascalCase } + attr :item_name, String + end + + # Suppress deprecation warning — we're testing the getter path + expect(resource_class.attribute_convention).to be_a(RestEasy::Conventions::PascalCase) + end + end + end + + # ── Default resolution ────────────────────────────────────────────── + + describe "default resolution" do + before(:all) do + module DefaultApi + extend RestEasy + # No conversions or attribute_convention set + end + + class DefaultApi::Thing < RestEasy::Resource + attr :my_field, String + end + end + + after(:all) do + Object.send(:remove_const, :DefaultApi) + end + + it "defaults json_attributes to PascalCase (matches 1.0.0 attribute_convention default)" do + expect(DefaultApi::Thing.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "defaults query_parameters to PascalCase" do + expect(DefaultApi::Thing.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + + it "falls back to PascalCase for a resource with no parent module" do + orphan = Class.new(RestEasy::Resource) + + expect(orphan.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + expect(orphan.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + end + end + + # ── Dynamic reconfiguration ───────────────────────────────────────── + + describe "dynamic reconfiguration" do + it "picks up json_attributes changes made after the converter is first read" do + module ReconfigJsonApi + extend RestEasy + end + class ReconfigJsonApi::Foo < RestEasy::Resource; end + + expect(ReconfigJsonApi::Foo.json_attribute_converter).to be_a(RestEasy::Conventions::PascalCase) + + ReconfigJsonApi.configure { conversions.json_attributes = :camelCase } + + expect(ReconfigJsonApi::Foo.json_attribute_converter).to be_a(RestEasy::Conventions::CamelCase) + ensure + Object.send(:remove_const, :ReconfigJsonApi) if defined?(ReconfigJsonApi) + end + + it "picks up query_parameters changes made after the converter is first read" do + module ReconfigQueryApi + extend RestEasy + end + class ReconfigQueryApi::Foo < RestEasy::Resource; end + + expect(ReconfigQueryApi::Foo.query_parameter_converter).to be_a(RestEasy::Conventions::PascalCase) + + ReconfigQueryApi.configure { conversions.query_parameters = :snake_case } + + expect(ReconfigQueryApi::Foo.query_parameter_converter).to be_a(RestEasy::Conventions::SnakeCase) + ensure + Object.send(:remove_const, :ReconfigQueryApi) if defined?(ReconfigQueryApi) + end + end +end diff --git a/spec/rest_easy/resource/crud_spec.rb b/spec/rest_easy/resource/crud_spec.rb index 984438d..fe1f441 100644 --- a/spec/rest_easy/resource/crud_spec.rb +++ b/spec/rest_easy/resource/crud_spec.rb @@ -7,7 +7,7 @@ module CrudTestApi configure do |config| config.base_url = "https://api.example.com/v1" - config.attribute_convention = :PascalCase + config.conversions.json_attributes = :PascalCase config.max_retries = 3 end end diff --git a/spec/rest_easy/resource/http_spec.rb b/spec/rest_easy/resource/http_spec.rb index dbe97ca..7b85c33 100644 --- a/spec/rest_easy/resource/http_spec.rb +++ b/spec/rest_easy/resource/http_spec.rb @@ -8,6 +8,7 @@ module HttpTestApi configure do |config| config.base_url = "https://api.example.com/v1" config.max_retries = 3 + config.conversions.json_attributes = :PascalCase end end diff --git a/spec/rest_easy/resource/inheritance_spec.rb b/spec/rest_easy/resource/inheritance_spec.rb index 7f2922f..ad0b19f 100644 --- a/spec/rest_easy/resource/inheritance_spec.rb +++ b/spec/rest_easy/resource/inheritance_spec.rb @@ -10,7 +10,7 @@ module InheritanceTestApi configure do |config| config.base_url = "https://api.example.com" - config.attribute_convention = :PascalCase + config.conversions.json_attributes = :PascalCase end end diff --git a/spec/rest_easy/resource_spec.rb b/spec/rest_easy/resource_spec.rb index 7f46c70..6df048a 100644 --- a/spec/rest_easy/resource_spec.rb +++ b/spec/rest_easy/resource_spec.rb @@ -577,6 +577,11 @@ def self.serialise(street, city) it "warns about undeclared API fields" do resource_class = Class.new(described_class) do + configure do + conversions.json_attributes = :PascalCase + debug true + end + attr :name, String end