permittable 0.8.0 → 0.10.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.
@@ -0,0 +1,72 @@
1
+ module Permittable
2
+ # A reusable field list — the same frozen data a contract's `fields` is,
3
+ # without the contract around it. The answer to the duplication a growing
4
+ # API produces: an `address` block wanted by three controllers, and an
5
+ # `update` contract that is the `create` contract with everything relaxed.
6
+ #
7
+ # AddressFields = Permittable.fields do
8
+ # required :city, :string, length: 1..80
9
+ # optional :zip, :string, format: /\A\d{5}\z/
10
+ # end
11
+ #
12
+ # UserFields = Permittable.fields do
13
+ # required :name, :string
14
+ # required :email, :string, format: URI::MailTo::EMAIL_REGEXP
15
+ # optional :plan, :string, in: %w[free pro], default: "free"
16
+ # optional :address do
17
+ # use AddressFields
18
+ # end
19
+ # end
20
+ #
21
+ # class UsersController < ApplicationController
22
+ # include Permittable
23
+ #
24
+ # permit_params :create, root: :user, model: User do
25
+ # use UserFields
26
+ # end
27
+ #
28
+ # # PATCH: same fields, nothing mandatory.
29
+ # permit_params :update, root: :user, model: User do
30
+ # use UserFields, optional: true
31
+ # end
32
+ # end
33
+ #
34
+ # A group is built by the same ContractBuilder a contract is, so every
35
+ # declaration is validated when the GROUP is defined — a typo fails at the
36
+ # group, once, rather than at each contract that uses it. It is frozen on
37
+ # construction and its fields are frozen hashes, so one group is safely
38
+ # shared by any number of contracts, and `use` splices without copying.
39
+ #
40
+ # What a group deliberately is not: a contract. It has no `root:`,
41
+ # `unknown:`, `model:` or `mode:` — those belong to the request being
42
+ # validated, not to a set of fields — and `finalize` is rejected for the
43
+ # same reason.
44
+ class FieldGroup
45
+ # The frozen field hashes, in declaration order.
46
+ attr_reader :fields
47
+
48
+ class << self
49
+ alias define new
50
+ end
51
+
52
+ def initialize(&block)
53
+ raise ArgumentError, "#{LABEL}: Permittable.fields requires a block declaring the fields" unless block
54
+
55
+ builder = ContractBuilder.new
56
+ @fields = builder.build(&block)
57
+ raise ArgumentError, "#{LABEL}: a field group must declare at least one field" if @fields.empty?
58
+ if builder.finalizer
59
+ raise ArgumentError, "#{LABEL}: finalize belongs to a contract, not a field group — " \
60
+ "declare it in the permit_params block that uses this group"
61
+ end
62
+
63
+ freeze
64
+ end
65
+
66
+ # Top-level field names, in declaration order — what `only:`/`except:`
67
+ # select from.
68
+ def names
69
+ @fields.map { |field| field[:name] }
70
+ end
71
+ end
72
+ end