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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 520468dff4cbd537cef3eec5d4d0c15607e0616143812746c559a7697294bc1c
4
- data.tar.gz: 338dc40010d0a02ab8819289ad64e9355fa2ee302fbb93ae82723b15a9b8d94c
3
+ metadata.gz: 2bee55f0854b77323d19e97acace58d7de0602aa1bb4500c19a0b9ea79f26013
4
+ data.tar.gz: 2587c8ae019b986041b712ddf9eb433666ff99efa7dcacbe1b4f6d2ae31ec66a
5
5
  SHA512:
6
- metadata.gz: b0f00c2a25862648b027d96b0491ddfdc6f838cf5fef010c8fa263436afb2ea463f85a3ea8b46ca3839e4036d8a8a73fd98a577fb78f2f8a204a1515b130022f
7
- data.tar.gz: 86a0f9d29e30d44d40d3af84604d458b8b8cffc190d9e960de3a3858d550161b33970520ea3d3e03d3ad3e221532f0fc28a48abd3882eb0c760a186465328d21
6
+ metadata.gz: b66f092c49e32afc330b5230184edad67af9c7ede2362befd80861978e90a22efe8dc86d9166121432416b9014fed2467654b294aa7c316dcf557ed13e987c1d
7
+ data.tar.gz: 45aaac0c0cb73909c1d75d9288fb1d3ab357a1a549295bebe160cbadb8cf0355b1bf1a112b14191fc2ca0d540b5a2667dd8b8b24ceedb01523e33086b6d9425d
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- easy_params (0.8.0)
4
+ easy_params (0.9.1)
5
5
  activemodel (>= 3.2)
6
6
 
7
7
  GEM
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
@@ -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
- public_send("#{attr}=", type.coerce(params.to_h[attr]))
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, collection: true, &block)
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, collection: false, &block)
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, collection: collection)
106
+ define_schema_method(param_name)
101
107
  end
102
108
 
103
- def define_schema_method(param_name, collection: false)
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, collection, default)
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, collection, default)
117
+ def create_schema_type(param_name, custom_type)
113
118
  type = schemas[param_name].new
114
- type = EasyParams::Types::Each.of(type) if collection
115
- customize_type(type, default)
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
@@ -41,6 +41,8 @@ module EasyParams
41
41
 
42
42
  # base interface for array of structs type
43
43
  class StructsCollection < Collection
44
+ include EasyParams::Composition
45
+
44
46
  def read_default
45
47
  @default
46
48
  end
@@ -4,9 +4,7 @@ module EasyParams
4
4
  module Types
5
5
  # base interface for simple types
6
6
  class Generic
7
- def array?
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 coerce(input)
21
- return if input.nil? && @default.nil?
22
- return self.class.new(@default) if input.nil? && @default.is_a?(Hash)
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module EasyParams
4
- VERSION = '0.8.0'
4
+ VERSION = '0.9.1'
5
5
  end
data/lib/easy_params.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'active_model'
4
4
  require 'bigdecimal/util'
5
+ require 'easy_params/composition'
5
6
  require 'easy_params/types/generic'
6
7
  require 'easy_params/types/collection'
7
8
  require 'easy_params/types/struct'
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.8.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