permittable 0.1.0 → 0.1.2

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: deed1778579c6d35f58ff18111114b00566ad0d6d2a92db5d4a9bfc3a730c6a6
4
- data.tar.gz: 57c83b026226096fcc96552d2df3a51577da12e704b5883fb4ba8c1110704433
3
+ metadata.gz: 84f1f4ca36c37a54ce5117ee6ec10bbd2e5dd5e9626e031c4dc35e8448ba1c35
4
+ data.tar.gz: c9ad5b56d78d44fd15de269547f431fd3441ec7233bc191cfa4c3ce391e8f19b
5
5
  SHA512:
6
- metadata.gz: 1c89bc7b9ff00e4246f0ac9bcc2937b97fb5ba8cdcfda1cbb5308d7d9cff5b4a7410eb48c5a185acd2b8feb82f595f771319edc33badd64ad1155f3d5e691b51
7
- data.tar.gz: a0087bd69e30d6739a9fc7af090b35cdbc268fbdad7cb57666d3fc71fc140a7b9e5304827e7d1789ff43ccc3f99789b576fbc8340d23595db38712e27abdb0db
6
+ metadata.gz: 0a1b6d1805eca305ba87b6ab7b78b238db6230255dcf14fc69096ff37f352c2cb4cc14199f1a68dfa19863dee745583b79f73510de8597a99dbc55c2cd1c01dc
7
+ data.tar.gz: 3630d2cba76050554c72cb763b2dd95f9442f2f12032b7b35932139f2ea0c887ab4f460178deff932d80d74d41b797ceac0f30cd9551984530c05d96a06c8b6a
data/CHANGELOG.md CHANGED
@@ -1,6 +1,32 @@
1
1
  <!-- CHANGELOG.md -->
2
2
 
3
+ ## 0.1.2 (2026-08-16)
4
+ <!-- title: gem metadata -->
5
+
6
+ Metadata-only release. `lib/` is byte-for-byte identical to 0.1.1, so upgrading changes nothing at runtime — it exists to publish the rewritten gem description, which RubyGems only refreshes on a new version.
7
+
8
+ ### Changed
9
+ - Rewrote the gem summary and description. Both now open with the comparison the README already draws — strong parameters say which keys may pass, a contract says what each field should be — rather than with implementation vocabulary. The schema-drift guard is presented as a consequence of contracts being class-level data instead of as one more bullet.
10
+
11
+ ### Internal
12
+ - The publish job creates the GitHub release itself, taking the title from this file's `<!-- title: ... -->` marker and the body from the matching section.
13
+ - Development dependencies: simplecov 0.22 → 1.1, `actions/checkout` 6 → 7.
14
+
15
+ ## 0.1.1 (2026-08-16)
16
+ <!-- title: maintenance release -->
17
+
18
+ Maintenance release. The public API and every documented behaviour are identical to 0.1.0; upgrading is a no-op.
19
+
20
+ ### Changed
21
+ - Internal style pass to satisfy the RuboCop config added in this release (hash alignment, guard clauses, anonymous block forwarding).
22
+ - `cast_datetime` folds `DateTime` into the `Time` branch whose body it already shared. Dispatch order is unchanged, so `DateTime` still matches ahead of `Date`, which it subclasses.
23
+ - Dropped `require "set"` from the filter parameter registry: `Set` has been autoloaded since Ruby 3.1 and the gemspec already floors at 3.2.
24
+
25
+ ### Added
26
+ - CI on Ruby 3.2 (RuboCop + RSpec) and tag-driven publishing to RubyGems via trusted publishing. Repository tooling only — not part of the packaged gem.
27
+
3
28
  ## 0.1.0 (2026-08-16)
29
+ <!-- title: initial release -->
4
30
 
5
31
  Initial extraction from [concerns_on_rails](https://github.com/VSN2015/concerns_on_rails) (developed there on `feature/permittable` as `ConcernsOnRails::Controllers::Permittable`; concerns_on_rails now depends on this gem and aliases that constant to `::Permittable`).
6
32
 
@@ -1,5 +1,3 @@
1
- require "set"
2
-
3
1
  module Permittable
4
2
  # Registry of sensitive parameter names (populated by `sensitive: true`
5
3
  # contract fields) surfaced to Rails' log filtering. Appending plain symbols
@@ -1,3 +1,3 @@
1
1
  module Permittable
2
- VERSION = "0.1.0".freeze
2
+ VERSION = "0.1.2".freeze
3
3
  end
data/lib/permittable.rb CHANGED
@@ -115,11 +115,11 @@ module Permittable
115
115
  ROUTING_KEYS = %w[controller action format].freeze
116
116
 
117
117
  NORMALIZERS = {
118
- squish: ->(v) { v.squish },
119
- strip: ->(v) { v.strip },
118
+ squish: ->(v) { v.squish },
119
+ strip: ->(v) { v.strip },
120
120
  downcase: ->(v) { v.downcase },
121
- upcase: ->(v) { v.upcase },
122
- email: ->(v) { v.strip.downcase },
121
+ upcase: ->(v) { v.upcase },
122
+ email: ->(v) { v.strip.downcase }
123
123
  }.freeze
124
124
 
125
125
  @registry_mutex = Mutex.new
@@ -271,8 +271,8 @@ module Permittable
271
271
  # (deterministic); explicit offsets are honoured and normalised to UTC.
272
272
  def cast_datetime(value)
273
273
  case value
274
- when ActiveSupport::TimeWithZone, Time then [:ok, value.to_time.utc]
275
- when DateTime then [:ok, value.to_time.utc]
274
+ # DateTime is listed here, ahead of Date, because it subclasses Date.
275
+ when ActiveSupport::TimeWithZone, Time, DateTime then [:ok, value.to_time.utc]
276
276
  when Date then [:ok, Time.utc(value.year, value.month, value.day)]
277
277
  when String then [:ok, DateTime.parse(value).to_time.utc]
278
278
  else [:error, "invalid_type"]
@@ -315,8 +315,8 @@ module Permittable
315
315
  @finalizer = nil
316
316
  end
317
317
 
318
- def build(&block)
319
- instance_eval(&block)
318
+ def build(&)
319
+ instance_eval(&)
320
320
  @fields.map(&:freeze).freeze
321
321
  end
322
322
 
@@ -331,12 +331,12 @@ module Permittable
331
331
 
332
332
  # `required :name` defaults the type to :string. A block instead of a
333
333
  # type declares a nested hash of sub-fields.
334
- def required(name, type = nil, **opts, &block)
335
- add_field(name, type, required: true, opts: opts, &block)
334
+ def required(name, type = nil, **opts, &)
335
+ add_field(name, type, required: true, opts: opts, &)
336
336
  end
337
337
 
338
- def optional(name, type = nil, **opts, &block)
339
- add_field(name, type, required: false, opts: opts, &block)
338
+ def optional(name, type = nil, **opts, &)
339
+ add_field(name, type, required: false, opts: opts, &)
340
340
  end
341
341
 
342
342
  # Array of scalars (`of:`, default :string) or, with a block, an array
@@ -384,9 +384,7 @@ module Permittable
384
384
 
385
385
  def field_name!(name)
386
386
  name = name.to_sym
387
- if @fields.any? { |f| f[:name] == name }
388
- raise ArgumentError, "#{LABEL}: field :#{name} is declared twice in the same contract"
389
- end
387
+ raise ArgumentError, "#{LABEL}: field :#{name} is declared twice in the same contract" if @fields.any? { |f| f[:name] == name }
390
388
 
391
389
  name
392
390
  end
@@ -408,9 +406,9 @@ module Permittable
408
406
  "(supported: #{SCALAR_TYPES.join(', ')})"
409
407
  end
410
408
 
411
- def nested_fields!(name, &block)
409
+ def nested_fields!(name, &)
412
410
  builder = ContractBuilder.new
413
- fields = builder.build(&block)
411
+ fields = builder.build(&)
414
412
  raise ArgumentError, "#{LABEL}: nested field :#{name} declares no sub-fields" if fields.empty?
415
413
  if builder.finalizer
416
414
  raise ArgumentError, "#{LABEL}: finalize is only available at the top level of a contract (found inside :#{name})"
@@ -638,7 +636,7 @@ module Permittable
638
636
  def render_invalid_parameters(error)
639
637
  ErrorEnvelope.render(
640
638
  self, message: error.message, status: error.status,
641
- code: "invalid_parameters", details: error.details
639
+ code: "invalid_parameters", details: error.details
642
640
  )
643
641
  end
644
642
 
@@ -650,12 +648,10 @@ module Permittable
650
648
  result = ActiveSupport::HashWithIndifferentAccess.new
651
649
  if source
652
650
  result = permittable_check_hash(rule[:fields], source, path: rule[:root] ? rule[:root].to_s : nil,
653
- unknown: rule[:unknown], top_level: !rule[:root], violations: violations)
651
+ unknown: rule[:unknown], top_level: !rule[:root], violations: violations)
654
652
  end
655
653
  # finalize only sees a hash every field vouched for — never garbage.
656
- if violations.empty? && rule[:finalize]
657
- result = permittable_run_finalize(rule[:finalize], result, violations)
658
- end
654
+ result = permittable_run_finalize(rule[:finalize], result, violations) if violations.empty? && rule[:finalize]
659
655
  return result if violations.empty?
660
656
 
661
657
  raise_invalid_parameters!(violations, status: source ? :unprocessable_entity : :bad_request)
@@ -757,9 +753,7 @@ module Permittable
757
753
 
758
754
  def permittable_check_array(field, value, path:, unknown:, violations:)
759
755
  before = violations.length
760
- if field[:length] && !Coercion.length_ok?(field[:length], value.length)
761
- violations << { param: path, code: "length" }
762
- end
756
+ violations << { param: path, code: "length" } if field[:length] && !Coercion.length_ok?(field[:length], value.length)
763
757
  out = value.each_with_index.map do |element, index|
764
758
  permittable_check_element(field, element, "#{path}[#{index}]", unknown: unknown, violations: violations)
765
759
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: permittable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Nguyen
@@ -30,10 +30,13 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: '9'
33
- description: 'Declarative per-action params contracts: strict typing/coercion, validation,
34
- defaults, machine-readable 422s, output reshaping (transform/finalize), and a boot-time
35
- schema-drift guard that fails the deploy when a permitted field''s column was dropped.
36
- Strong parameters with types, validation, and drift detection.'
33
+ description: 'Strong parameters answer only which keys may pass. A Permittable contract
34
+ also says what each field should be: it casts the value to a declared type, validates
35
+ bounds and formats, applies defaults, and renders every failure as a 422 that names
36
+ the offending parameter. Because a contract is class-level data rather than code
37
+ inside the action, it can also be checked against the database when the controller
38
+ loads, so a column dropped by a migration fails the deploy instead of the request.
39
+ activesupport is the only runtime dependency.'
37
40
  email:
38
41
  - doctorit@gmail.com
39
42
  executables: []
@@ -72,8 +75,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
72
75
  - !ruby/object:Gem::Version
73
76
  version: '0'
74
77
  requirements: []
75
- rubygems_version: 3.4.10
78
+ rubygems_version: 3.4.19
76
79
  signing_key:
77
80
  specification_version: 4
78
- summary: Typed, validated params contracts for Rails controllers + schema-drift guard
81
+ summary: Strong parameters for Rails that also know types, bounds, and defaults
79
82
  test_files: []