easy_params 0.8.0 → 0.9.1
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 +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +71 -0
- data/lib/easy_params/base.rb +15 -10
- data/lib/easy_params/composition.rb +45 -0
- data/lib/easy_params/types/collection.rb +2 -0
- data/lib/easy_params/types/generic.rb +5 -3
- data/lib/easy_params/types/struct.rb +15 -3
- data/lib/easy_params/version.rb +1 -1
- data/lib/easy_params.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2bee55f0854b77323d19e97acace58d7de0602aa1bb4500c19a0b9ea79f26013
|
|
4
|
+
data.tar.gz: 2587c8ae019b986041b712ddf9eb433666ff99efa7dcacbe1b4f6d2ae31ec66a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b66f092c49e32afc330b5230184edad67af9c7ede2362befd80861978e90a22efe8dc86d9166121432416b9014fed2467654b294aa7c316dcf557ed13e987c1d
|
|
7
|
+
data.tar.gz: 45aaac0c0cb73909c1d75d9288fb1d3ab357a1a549295bebe160cbadb8cf0355b1bf1a112b14191fc2ca0d540b5a2667dd8b8b24ceedb01523e33086b6d9425d
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
|
@@ -173,6 +173,77 @@ params.comments.first.created_at # => Date.today
|
|
|
173
173
|
- **Replaces parent**: The new subclass completely replaces the original schema class
|
|
174
174
|
- **Collection support**: Works with both `has` (struct) and `each` (collection) parameters
|
|
175
175
|
|
|
176
|
+
### Composition and Owner Context
|
|
177
|
+
|
|
178
|
+
EasyParams supports composition through an owner relationship, allowing nested objects to access methods from their parent objects or an external owner object. This is particularly useful for conditional validations and accessing context from nested structures.
|
|
179
|
+
|
|
180
|
+
Every `EasyParams::Base` instance has an `owner` attribute that is automatically set for nested objects:
|
|
181
|
+
- Objects created with `has` get their parent as the owner
|
|
182
|
+
- Objects in collections created with `each` get the collection as their owner, and the collection gets the parent as its owner
|
|
183
|
+
|
|
184
|
+
You can access methods on the owner chain using the `owner_` prefix:
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
class Owner
|
|
188
|
+
def check_name?
|
|
189
|
+
true
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def check_address_city?
|
|
193
|
+
true
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def check_phone_number?
|
|
197
|
+
true
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
class UserParams < EasyParams::Base
|
|
202
|
+
integer :id
|
|
203
|
+
string :name, presence: { if: :owner_check_name? }
|
|
204
|
+
|
|
205
|
+
has :address do
|
|
206
|
+
string :street
|
|
207
|
+
string :city, presence: { if: :owner_check_address_city? }
|
|
208
|
+
string :state
|
|
209
|
+
string :zip
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
each :phones do
|
|
213
|
+
string :number, presence: { if: :owner_check_phone_number? }
|
|
214
|
+
string :type
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Use with an external owner object
|
|
219
|
+
owner = Owner.new
|
|
220
|
+
params = UserParams.new(
|
|
221
|
+
id: 1,
|
|
222
|
+
address: { street: '123 Main St', city: nil },
|
|
223
|
+
phones: [{ number: nil, type: 'home' }]
|
|
224
|
+
)
|
|
225
|
+
params.owner = owner
|
|
226
|
+
|
|
227
|
+
# Validations will use the owner's methods
|
|
228
|
+
params.valid? # => false
|
|
229
|
+
params.errors[:name] # => ["can't be blank"]
|
|
230
|
+
params.address.errors[:city] # => ["can't be blank"]
|
|
231
|
+
params.phones[0].errors[:number] # => ["can't be blank"]
|
|
232
|
+
|
|
233
|
+
# Owner relationships are automatically established
|
|
234
|
+
params.owner # => owner
|
|
235
|
+
params.address.owner # => params (parent)
|
|
236
|
+
params.phones.owner # => params (parent)
|
|
237
|
+
params.phones[0].owner # => params.phones (collection)
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Key Features:**
|
|
241
|
+
- **Automatic owner setup**: Nested objects automatically get their parent as owner
|
|
242
|
+
- **Owner chain**: The `owner_` prefix searches up the owner chain to find methods
|
|
243
|
+
- **External owners**: Set an external object as owner to provide additional context
|
|
244
|
+
- **Conditional validations**: Use owner methods in validation conditions (`if:`, `unless:`)
|
|
245
|
+
- **Nested access**: Works at any nesting level - nested objects can access parent methods
|
|
246
|
+
|
|
176
247
|
### Validation errors
|
|
177
248
|
|
|
178
249
|
```ruby
|
data/lib/easy_params/base.rb
CHANGED
|
@@ -6,12 +6,18 @@ module EasyParams
|
|
|
6
6
|
include ActiveModel::Model
|
|
7
7
|
include EasyParams::Types::Struct
|
|
8
8
|
include EasyParams::Validation
|
|
9
|
+
include EasyParams::Composition
|
|
9
10
|
|
|
10
11
|
attr_writer :default
|
|
11
12
|
|
|
12
13
|
def initialize(params = {})
|
|
13
14
|
self.class.schema.each do |attr, type|
|
|
14
|
-
|
|
15
|
+
coerced_type = type.coerce(params.to_h[attr])
|
|
16
|
+
if coerced_type && (type.is_a?(EasyParams::Base) || type.is_a?(EasyParams::Types::StructsCollection))
|
|
17
|
+
coerced_type.owner = self
|
|
18
|
+
end
|
|
19
|
+
coerced_type.each { |v| v.owner = coerced_type } if coerced_type.is_a?(EasyParams::Types::StructsCollection)
|
|
20
|
+
public_send("#{attr}=", coerced_type)
|
|
15
21
|
end
|
|
16
22
|
end
|
|
17
23
|
|
|
@@ -46,7 +52,7 @@ module EasyParams
|
|
|
46
52
|
raise ArgumentError, "definition for attribute #{param_name.inspect} must be a subclass of EasyParams::Base"
|
|
47
53
|
end
|
|
48
54
|
|
|
49
|
-
handle_schema_definition(param_name, definition,
|
|
55
|
+
handle_schema_definition(param_name, definition, &block)
|
|
50
56
|
type = EasyParams::Types::Each.of(schemas[param_name].new)
|
|
51
57
|
type = customize_type(type, default, &normalize)
|
|
52
58
|
attribute(param_name, type)
|
|
@@ -95,24 +101,23 @@ module EasyParams
|
|
|
95
101
|
type
|
|
96
102
|
end
|
|
97
103
|
|
|
98
|
-
def handle_schema_definition(param_name, definition = nil,
|
|
104
|
+
def handle_schema_definition(param_name, definition = nil, &block)
|
|
99
105
|
schemas[param_name] = definition || Class.new(EasyParams::Base).tap { |c| c.class_eval(&block) }
|
|
100
|
-
define_schema_method(param_name
|
|
106
|
+
define_schema_method(param_name)
|
|
101
107
|
end
|
|
102
108
|
|
|
103
|
-
def define_schema_method(param_name
|
|
109
|
+
def define_schema_method(param_name)
|
|
104
110
|
define_singleton_method("#{param_name}_schema") do |&block|
|
|
105
|
-
default = schema[param_name].read_default
|
|
106
111
|
schemas[param_name] = Class.new(schemas[param_name]).tap { |c| c.class_eval(&block) }
|
|
107
|
-
type = create_schema_type(param_name,
|
|
112
|
+
type = create_schema_type(param_name, schema[param_name])
|
|
108
113
|
attribute(param_name, type)
|
|
109
114
|
end
|
|
110
115
|
end
|
|
111
116
|
|
|
112
|
-
def create_schema_type(param_name,
|
|
117
|
+
def create_schema_type(param_name, custom_type)
|
|
113
118
|
type = schemas[param_name].new
|
|
114
|
-
type = EasyParams::Types::Each.of(type) if
|
|
115
|
-
customize_type(type,
|
|
119
|
+
type = EasyParams::Types::Each.of(type) if custom_type.is_a?(Types::StructsCollection)
|
|
120
|
+
customize_type(type, custom_type.read_default, &custom_type.normalize_proc)
|
|
116
121
|
end
|
|
117
122
|
end
|
|
118
123
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EasyParams
|
|
4
|
+
# Provides composition helpers for EasyParams components.
|
|
5
|
+
module Composition
|
|
6
|
+
def self.included(base)
|
|
7
|
+
base.attr_accessor :owner
|
|
8
|
+
base.include(InstanceMethods)
|
|
9
|
+
base.extend(ClassMethods)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
module ClassMethods # rubocop:disable Style/Documentation
|
|
13
|
+
def part_of(owner_name)
|
|
14
|
+
@owner_name = owner_name
|
|
15
|
+
alias_method owner_name, :owner
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
module InstanceMethods # rubocop:disable Style/Documentation
|
|
20
|
+
def method_missing(name, *attrs, **kwargs, &block)
|
|
21
|
+
return super unless name.to_s.start_with?('owner_')
|
|
22
|
+
|
|
23
|
+
owner_method = name.to_s.sub('owner_', '').to_sym
|
|
24
|
+
return super unless (handler = owners_chain.lazy.detect { |o| o.respond_to?(owner_method) })
|
|
25
|
+
|
|
26
|
+
handler.public_send(owner_method, *attrs, **kwargs, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def respond_to_missing?(method_name, include_private = false)
|
|
30
|
+
return super unless method_name.to_s.start_with?('owner_')
|
|
31
|
+
|
|
32
|
+
owners_chain.lazy.any? { |o| o.respond_to?(method_name.to_s.sub('owner_', '').to_sym, include_private) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def owners_chain
|
|
38
|
+
@owners_chain ||= Enumerator.new do |y|
|
|
39
|
+
obj = self
|
|
40
|
+
y << obj = obj.owner while obj.respond_to?(:owner)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -4,9 +4,7 @@ module EasyParams
|
|
|
4
4
|
module Types
|
|
5
5
|
# base interface for simple types
|
|
6
6
|
class Generic
|
|
7
|
-
|
|
8
|
-
@title == :array
|
|
9
|
-
end
|
|
7
|
+
attr_reader :normalize_proc
|
|
10
8
|
|
|
11
9
|
def initialize(title, default = nil, normalize_proc = nil, &coerce_proc)
|
|
12
10
|
@title = title
|
|
@@ -15,6 +13,10 @@ module EasyParams
|
|
|
15
13
|
@normalize_proc = normalize_proc
|
|
16
14
|
end
|
|
17
15
|
|
|
16
|
+
def array?
|
|
17
|
+
@title == :array
|
|
18
|
+
end
|
|
19
|
+
|
|
18
20
|
def default(value)
|
|
19
21
|
self.class.new(@title, value, @normalize_proc, &@coerce_proc)
|
|
20
22
|
end
|
|
@@ -12,14 +12,26 @@ module EasyParams
|
|
|
12
12
|
@default
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
+
def normalize_proc
|
|
16
|
+
@normalize_proc
|
|
17
|
+
end
|
|
18
|
+
|
|
15
19
|
def default(value)
|
|
16
20
|
self.default = value
|
|
17
21
|
self
|
|
18
22
|
end
|
|
19
23
|
|
|
20
|
-
def
|
|
21
|
-
|
|
22
|
-
|
|
24
|
+
def normalize(&block)
|
|
25
|
+
@normalize_proc = block
|
|
26
|
+
self
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def coerce(value)
|
|
30
|
+
return if value.nil? && @default.nil?
|
|
31
|
+
|
|
32
|
+
input = value || @default
|
|
33
|
+
input = @normalize_proc.call(input) if @normalize_proc
|
|
34
|
+
return self.class.new(input) if input.is_a?(Hash)
|
|
23
35
|
|
|
24
36
|
self.class.new(input)
|
|
25
37
|
end
|
data/lib/easy_params/version.rb
CHANGED
data/lib/easy_params.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: easy_params
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.9.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Andrii Baran
|
|
@@ -48,6 +48,7 @@ files:
|
|
|
48
48
|
- easy_params.gemspec
|
|
49
49
|
- lib/easy_params.rb
|
|
50
50
|
- lib/easy_params/base.rb
|
|
51
|
+
- lib/easy_params/composition.rb
|
|
51
52
|
- lib/easy_params/types.rb
|
|
52
53
|
- lib/easy_params/types/collection.rb
|
|
53
54
|
- lib/easy_params/types/generic.rb
|