smart_properties 1.13.1 → 1.16.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
- SHA1:
3
- metadata.gz: b393bac2287b2fc8bc96acdfe0da75ee774d9193
4
- data.tar.gz: f6cc1a80370cdd4d2c361c4eaf6b933841747a10
2
+ SHA256:
3
+ metadata.gz: 044a13ef450738b8342644983451e7659ba6e33116801be84206fa9197e42c94
4
+ data.tar.gz: 709f79b9de54975dc92c8b47bb68db67b81c14f20f60d21358098060b34629db
5
5
  SHA512:
6
- metadata.gz: f878809a07157695137651ad2a20daf44b7dbbec3beae5685fc05c9512f6c165a366d6cf9b67256912bc8e1e50acb3fff98245997364560e894d35a816f46c0d
7
- data.tar.gz: 70571022057eb569d0b0702e25218d99a052be2506fb63d43262ef31ce869a05f462b38e3dbce681f2368b52516f4321328207cb40f9a0311df5cd9989cb8f8b
6
+ metadata.gz: 0a9d6e5383e26cc9143553fe54c5f030be3d40d1927b4613f0f87c751fb3fdf3741afb0aeaaf22336a229070f3e05a1bbe25751736b50e7f8cf3d4d1c8b74909
7
+ data.tar.gz: bf90c5eec8fa7059a044f14b810583492d3775f254c3a59c0cf1985b0a2343fa2830777a0c6e28b91b2e7eed5ca528189862f6effa8724d094775168cf17d0a8
@@ -0,0 +1,24 @@
1
+ name: Testing
2
+
3
+ on:
4
+ push:
5
+ branches: [ master ]
6
+ pull_request:
7
+ branches: [ master ]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ test:
12
+
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - uses: actions/checkout@v2
17
+ - name: Set up Ruby
18
+ uses: ruby/setup-ruby@v1
19
+ with:
20
+ ruby-version: 2.6
21
+ - name: Install dependencies
22
+ run: bundle install
23
+ - name: Run tests
24
+ run: bundle exec rake
data/.gitignore CHANGED
@@ -15,6 +15,7 @@ spec/reports
15
15
  test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
+ .idea/
18
19
 
19
20
  .rspec
20
- .rvmrc
21
+ .rvmrc
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 2.4.1
1
+ 2.6.6
data/README.md CHANGED
@@ -171,6 +171,16 @@ class Article
171
171
  end
172
172
  ```
173
173
 
174
+ There are also a set of common validation helpers you may use. These common
175
+ cases are provided to help avoid rewriting validation logic that occurs
176
+ often. These validations can be found in the `SmartProperties::Validations` module.
177
+
178
+ ```ruby
179
+ class Article
180
+ property :view_count, accepts: Ancestor.must_be(type: Number)
181
+ end
182
+ ```
183
+
174
184
  #### Default values
175
185
 
176
186
  There is also support for default values. Simply use the `:default`
@@ -1,12 +1,14 @@
1
1
  module SmartProperties
2
2
  class Property
3
3
  MODULE_REFERENCE = :"@_smart_properties_method_scope"
4
+ ALLOWED_DEFAULT_CLASSES = [Proc, Numeric, String, Range, TrueClass, FalseClass, NilClass, Symbol, Module].freeze
4
5
 
5
6
  attr_reader :name
6
7
  attr_reader :converter
7
8
  attr_reader :accepter
8
9
  attr_reader :reader
9
10
  attr_reader :instance_variable_name
11
+ attr_reader :writable
10
12
 
11
13
  def self.define(scope, name, options = {})
12
14
  new(name, options).tap { |p| p.define(scope) }
@@ -21,10 +23,16 @@ module SmartProperties
21
23
  @accepter = attrs.delete(:accepts)
22
24
  @required = attrs.delete(:required)
23
25
  @reader = attrs.delete(:reader)
26
+ @writable = attrs.delete(:writable)
24
27
  @reader ||= @name
25
28
 
26
29
  @instance_variable_name = :"@#{name}"
27
30
 
31
+ unless ALLOWED_DEFAULT_CLASSES.any? { |cls| @default.is_a?(cls) }
32
+ raise ConfigurationError, "Default attribute value #{@default.inspect} cannot be specified as literal, "\
33
+ "use the syntax `default: -> { ... }` instead."
34
+ end
35
+
28
36
  unless attrs.empty?
29
37
  raise ConfigurationError, "SmartProperties do not support the following configuration options: #{attrs.keys.map { |m| m.to_s }.sort.join(', ')}."
30
38
  end
@@ -46,6 +54,11 @@ module SmartProperties
46
54
  !null_object?(get(scope))
47
55
  end
48
56
 
57
+ def writable?
58
+ return true if @writable.nil?
59
+ @writable
60
+ end
61
+
49
62
  def convert(scope, value)
50
63
  return value unless converter
51
64
  return value if null_object?(value)
@@ -59,7 +72,7 @@ module SmartProperties
59
72
  end
60
73
 
61
74
  def default(scope)
62
- @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default
75
+ @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default.dup
63
76
  end
64
77
 
65
78
  def accepts?(value, scope)
@@ -98,8 +111,11 @@ module SmartProperties
98
111
  scope.send(:define_method, reader) do
99
112
  property.get(self)
100
113
  end
101
- scope.send(:define_method, :"#{name}=") do |value|
102
- property.set(self, value)
114
+
115
+ if writable?
116
+ scope.send(:define_method, :"#{name}=") do |value|
117
+ property.set(self, value)
118
+ end
103
119
  end
104
120
  end
105
121
 
@@ -141,12 +157,19 @@ module SmartProperties
141
157
  rescue NoMethodError => error
142
158
  # BasicObject does not respond to #nil? by default, so we need to double
143
159
  # check if somebody implemented it and it fails internally or if the
144
- # error occured because the method is actually not present. In the former
145
- # case, we want to raise the exception because there is something wrong
146
- # with the implementation of object#nil?. In the latter case we treat the
147
- # object as truthy because we don't know better.
148
- raise error if (class << object; self; end).public_instance_methods.include?(:nil?)
149
- false
160
+ # error occured because the method is actually not present.
161
+
162
+ # This is a workaround for the fact that #singleton_class is defined on Object, but not BasicObject.
163
+ the_singleton_class = (class << object; self; end)
164
+
165
+ if the_singleton_class.public_instance_methods.include?(:nil?)
166
+ # object defines #nil?, but it raised NoMethodError,
167
+ # something is wrong with the implementation, so raise the exception.
168
+ raise error
169
+ else
170
+ # treat the object as truthy because we don't know better.
171
+ false
172
+ end
150
173
  end
151
174
  end
152
175
  end
@@ -5,16 +5,19 @@ module SmartProperties
5
5
  attr_reader :parent
6
6
 
7
7
  def self.for(scope)
8
- parent = scope.ancestors[1..-1].find do |ancestor|
9
- ancestor.ancestors.include?(SmartProperties) && ancestor != SmartProperties
8
+ parents = scope.ancestors[1..-1].select do |ancestor|
9
+ ancestor.ancestors.include?(SmartProperties) &&
10
+ ancestor != scope &&
11
+ ancestor != SmartProperties
10
12
  end
11
13
 
12
- if parent.nil?
13
- new
14
- else
15
- parent.properties.register(collection = new)
16
- collection
14
+ collection = new
15
+
16
+ parents.each do |parent|
17
+ parent.properties.register(collection)
17
18
  end
19
+
20
+ collection
18
21
  end
19
22
 
20
23
  def initialize
@@ -73,7 +76,7 @@ module SmartProperties
73
76
  end
74
77
 
75
78
  def refresh(parent_collection)
76
- @collection_with_parent_collection = parent_collection.merge(collection)
79
+ @collection_with_parent_collection.merge!(parent_collection)
77
80
  notify_children
78
81
  nil
79
82
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module SmartProperties
3
+ module Validations
4
+ class Ancestor
5
+ include SmartProperties
6
+
7
+ property! :type, accepts: ->(type) { type.is_a?(Class) }
8
+
9
+ def validate(klass)
10
+ klass.is_a?(Class) && klass < type
11
+ end
12
+
13
+ def to_proc
14
+ validator = self
15
+ ->(klass) { validator.validate(klass) }
16
+ end
17
+
18
+ def to_s
19
+ "subclasses of #{type.to_s}"
20
+ end
21
+
22
+ class << self
23
+ alias_method :must_be, :new
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SmartProperties
4
+ module Validations
5
+ end
6
+ end
7
+
8
+ require_relative 'validations/ancestor'
@@ -1,3 +1,3 @@
1
1
  module SmartProperties
2
- VERSION = "1.13.1"
2
+ VERSION = "1.16.1"
3
3
  end
@@ -91,6 +91,13 @@ module SmartProperties
91
91
  protected :property!
92
92
  end
93
93
 
94
+ module ModuleMethods
95
+ def included(target)
96
+ super
97
+ target.include(SmartProperties)
98
+ end
99
+ end
100
+
94
101
  class << self
95
102
  private
96
103
 
@@ -102,6 +109,7 @@ module SmartProperties
102
109
  #
103
110
  def included(base)
104
111
  base.extend(ClassMethods)
112
+ base.extend(ModuleMethods) if base.is_a?(Module)
105
113
  end
106
114
  end
107
115
 
@@ -171,3 +179,4 @@ require_relative 'smart_properties/property_collection'
171
179
  require_relative 'smart_properties/property'
172
180
  require_relative 'smart_properties/errors'
173
181
  require_relative 'smart_properties/version'
182
+ require_relative 'smart_properties/validations'
@@ -12,6 +12,10 @@ Gem::Specification.new do |gem|
12
12
  gem.summary = %q{SmartProperties – Ruby accessors on steroids}
13
13
  gem.homepage = ""
14
14
 
15
+ gem.metadata = {
16
+ "source_code_uri" => "https://github.com/t6d/smart_properties"
17
+ }
18
+
15
19
  gem.files = `git ls-files`.split($\)
16
20
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
21
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
@@ -20,6 +24,6 @@ Gem::Specification.new do |gem|
20
24
  gem.version = SmartProperties::VERSION
21
25
 
22
26
  gem.add_development_dependency "rspec", "~> 3.0"
23
- gem.add_development_dependency "rake", "~> 10.0"
27
+ gem.add_development_dependency "rake", "~> 13.0"
24
28
  gem.add_development_dependency "pry"
25
29
  end
data/spec/base_spec.rb CHANGED
@@ -41,7 +41,7 @@ RSpec.describe SmartProperties do
41
41
  default_title = double(to_title: 'chunky')
42
42
 
43
43
  DummyClass.new do
44
- property :title, converts: :to_title, accepts: String, required: true, default: default_title
44
+ property :title, converts: :to_title, accepts: String, required: true, default: -> { default_title }
45
45
  end
46
46
  end
47
47
 
@@ -49,7 +49,7 @@ RSpec.describe SmartProperties do
49
49
 
50
50
  it "should return a property's configuration with #to_h" do
51
51
  expect(klass.properties.values.first.to_h).to match(
52
- accepter: String, converter: :to_title, default: an_instance_of(RSpec::Mocks::Double),
52
+ accepter: String, converter: :to_title, default: an_instance_of(Proc),
53
53
  instance_variable_name: :@title, name: :title, reader: :title, required: true
54
54
  )
55
55
  end
@@ -13,5 +13,38 @@ RSpec.describe SmartProperties, 'configuration error' do
13
13
 
14
14
  expect(&invalid_property_definition).to raise_error(SmartProperties::ConfigurationError, "SmartProperties do not support the following configuration options: invalid_option_1, invalid_option_2, invalid_option_3.")
15
15
  end
16
+
17
+ it "should accept default values that can't be mutated" do
18
+ valid_property_definition = lambda do
19
+ klass.class_eval do
20
+ property :proc, default: -> { }
21
+ property :numeric_float, default: 1.23
22
+ property :numeric_int, default: 456
23
+ property :string, default: "abc"
24
+ property :range, default: 123...456
25
+ property :bool_true, default: true
26
+ property :bool_false, default: false
27
+ property :nil, default: nil
28
+ property :symbol, default: :abc
29
+ property :module, default: Integer
30
+ end
31
+ end
32
+
33
+ expect(&valid_property_definition).not_to raise_error
34
+ end
35
+
36
+ it "should not accept default values that may be mutated" do
37
+ invalid_property_definition = lambda do
38
+ klass.class_eval do
39
+ property :title, default: []
40
+ end
41
+ end
42
+
43
+ expect(&invalid_property_definition).to(
44
+ raise_error(SmartProperties::ConfigurationError,
45
+ "Default attribute value [] cannot be specified as literal, "\
46
+ "use the syntax `default: -> { ... }` instead.")
47
+ )
48
+ end
16
49
  end
17
50
  end
@@ -48,4 +48,72 @@ RSpec.describe SmartProperties, 'default values' do
48
48
  end
49
49
  end
50
50
  end
51
+
52
+ context "when defining a new property with a literal default value" do
53
+ context 'with a numeric default' do
54
+ subject(:klass) { DummyClass.new { property :var, default: 123 } }
55
+
56
+ it 'accepts the default and returns it' do
57
+ instance = klass.new
58
+ expect(instance.var).to(be(123))
59
+ end
60
+ end
61
+
62
+ context 'with a string default' do
63
+ DEFAULT_VALUE = 'a string'
64
+ subject(:klass) { DummyClass.new { property :var, default: DEFAULT_VALUE } }
65
+
66
+ it 'accepts the default and returns it' do
67
+ instance = klass.new
68
+ expect(instance.var).to(eq(DEFAULT_VALUE))
69
+ end
70
+
71
+ it 'returns a copy of the string' do
72
+ instance = klass.new
73
+ expect(instance.var).to_not(be(DEFAULT_VALUE))
74
+ end
75
+
76
+ it 'mutating the instance variable does not mutate the original' do
77
+ instance = klass.new
78
+ instance.var[0] = 'o'
79
+ expect(DEFAULT_VALUE).to(eq('a string'))
80
+ end
81
+ end
82
+
83
+ context 'with a range default' do
84
+ subject(:klass) { DummyClass.new { property :var, default: 1..2 } }
85
+
86
+ it 'accepts the default and returns it' do
87
+ instance = klass.new
88
+ expect(instance.var).to(eq(1..2))
89
+ end
90
+ end
91
+
92
+ context 'with a true default' do
93
+ subject(:klass) { DummyClass.new { property :var, default: true } }
94
+
95
+ it 'accepts the default and returns it' do
96
+ instance = klass.new
97
+ expect(instance.var).to(be(true))
98
+ end
99
+ end
100
+
101
+ context 'with a false default' do
102
+ subject(:klass) { DummyClass.new { property :var, default: false } }
103
+
104
+ it 'accepts the default and returns it' do
105
+ instance = klass.new
106
+ expect(instance.var).to(be(false))
107
+ end
108
+ end
109
+
110
+ context 'with a symbol default' do
111
+ subject(:klass) { DummyClass.new { property :var, default: :foo } }
112
+
113
+ it 'accepts the default and returns it' do
114
+ instance = klass.new
115
+ expect(instance.var).to(be(:foo))
116
+ end
117
+ end
118
+ end
51
119
  end
@@ -225,4 +225,44 @@ RSpec.describe SmartProperties, 'intheritance' do
225
225
  end
226
226
  end
227
227
  end
228
+
229
+ it "supports multiple inheritance through modules" do
230
+ m = Module.new do
231
+ include SmartProperties
232
+ property :m, default: 1
233
+ end
234
+
235
+ n = Module.new do
236
+ include SmartProperties
237
+ property :n, default: 2
238
+ end
239
+
240
+ o = Module.new {}
241
+
242
+ klass = Class.new do
243
+ include m
244
+ include o
245
+ include n
246
+ end
247
+
248
+ n.module_eval do
249
+ property :p, default: 3
250
+ end
251
+
252
+ instance = klass.new
253
+
254
+ expect(instance.m).to eq(1)
255
+ expect(instance.n).to eq(2)
256
+ expect(instance.p).to eq(3)
257
+
258
+ expect { klass.new(m: 4, n: 5, p: 6) }.to_not raise_error
259
+
260
+ klass2 = Class.new do
261
+ include n
262
+ include m
263
+ end
264
+
265
+ expect { klass.new(m: 4, n: 5, p: 6) }.to_not raise_error
266
+ expect { klass2.new(m: 4, n: 5, p: 6) }.to_not raise_error
267
+ end
228
268
  end
@@ -26,4 +26,14 @@ RSpec.describe SmartProperties, "property collection caching:" do
26
26
  expect(subsubclass.properties.keys - expected_names).to be_empty
27
27
  expect(subsubclass.properties.to_hash.keys - expected_names).to be_empty
28
28
  end
29
+
30
+ specify "a SmartProperty enabled object should not check itself for properties if prepended" do
31
+ expect do
32
+ base_class = DummyClass.new {
33
+ prepend Module.new
34
+ property :title
35
+ }
36
+ expect(base_class.new).to have_smart_property(:title)
37
+ end.not_to raise_error(SystemStackError)
38
+ end
29
39
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+ require 'spec_helper'
3
+ require 'smart_properties/validations/ancestor'
4
+
5
+ RSpec.describe SmartProperties::Validations::Ancestor, 'validates ancestor' do
6
+ context 'used to validate the ancestor of a smart_properties value' do
7
+ let!(:test_base) do
8
+ Class.new do
9
+ def self.to_s
10
+ 'TestBase'
11
+ end
12
+ end
13
+ end
14
+
15
+ subject(:klass) do
16
+ test_base_ref = test_base
17
+
18
+ DummyClass.new do
19
+ property :visible, accepts: SmartProperties::Validations::Ancestor.must_be(type: test_base_ref)
20
+ end
21
+ end
22
+
23
+ it 'should return an error for any non Class based value' do
24
+ expect { subject.new(visible: true) }
25
+ .to raise_error(SmartProperties::InvalidValueError, /Only accepts: subclasses of TestBase/)
26
+ end
27
+
28
+ it 'should return an error for any Class instance instead of a class type' do
29
+ non_ancestor_class = Class.new
30
+
31
+ expect { subject.new(visible: non_ancestor_class.new) }
32
+ .to raise_error(SmartProperties::InvalidValueError, /Only accepts: subclasses of TestBase/)
33
+ end
34
+
35
+
36
+ it 'should return an error for any Class instance instead of a class type even if it is a child' do
37
+ test_base_child = Class.new(test_base)
38
+
39
+ expect { subject.new(visible: test_base_child.new) }
40
+ .to raise_error(SmartProperties::InvalidValueError, /Only accepts: subclasses of TestBase/)
41
+ end
42
+
43
+ it 'should return an error for a Class type that is not a child of the required ancestor' do
44
+ non_ancestor_class = Class.new
45
+
46
+ expect { subject.new(visible: non_ancestor_class) }
47
+ .to raise_error(SmartProperties::InvalidValueError, /Only accepts: subclasses of TestBase/)
48
+ end
49
+
50
+ it 'should return an error if the class is the ancestor itself' do
51
+ expect { subject.new(visible: test_base) }
52
+ .to raise_error(SmartProperties::InvalidValueError, /Only accepts: subclasses of TestBase/)
53
+ end
54
+
55
+ it 'should succeed if the given class is a subtype ' do
56
+ test_valid_class = Class.new(test_base)
57
+
58
+ expect { subject.new(visible: test_valid_class) }
59
+ .not_to raise_error
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe SmartProperties, 'writable properties' do
6
+ context 'when a property is defined as not writable there should be no accessor for the property' do
7
+ subject(:klass) { DummyClass.new { property :id, writable: false } }
8
+
9
+ it "should throw a no method error when trying to set the property" do
10
+ new_class_instance = klass.new(id: 42)
11
+
12
+ expect(new_class_instance.id).to eq(42)
13
+ expect { new_class_instance.id = 50 }.to raise_error(NoMethodError)
14
+ expect(new_class_instance.id).to eq(42)
15
+ end
16
+ end
17
+
18
+ context 'when a property is defined as writable there should be an accessor available' do
19
+ subject(:klass) { DummyClass.new { property :id, writable: true } }
20
+
21
+ it "should allow changing of the property" do
22
+ new_class_instance = klass.new(id: 42)
23
+
24
+ new_class_instance.id = 50
25
+ expect(new_class_instance.id).to eq(50)
26
+ end
27
+ end
28
+
29
+ context 'when writable is not defined on the property it should default to being writable' do
30
+ subject(:klass) { DummyClass.new { property :id } }
31
+
32
+ it "should allow changing of the property" do
33
+ new_class_instance = klass.new(id: 42)
34
+
35
+ new_class_instance.id = 50
36
+ expect(new_class_instance.id).to eq(50)
37
+ end
38
+ end
39
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: smart_properties
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.1
4
+ version: 1.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Tennhard
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-29 00:00:00.000000000 Z
11
+ date: 2021-09-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rspec
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: '13.0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: '13.0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: pry
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -62,6 +62,7 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".github/workflows/testing.yml"
65
66
  - ".gitignore"
66
67
  - ".ruby-version"
67
68
  - ".yardopts"
@@ -75,6 +76,8 @@ files:
75
76
  - lib/smart_properties/errors.rb
76
77
  - lib/smart_properties/property.rb
77
78
  - lib/smart_properties/property_collection.rb
79
+ - lib/smart_properties/validations.rb
80
+ - lib/smart_properties/validations/ancestor.rb
78
81
  - lib/smart_properties/version.rb
79
82
  - smart_properties.gemspec
80
83
  - spec/acceptance_checking_spec.rb
@@ -89,10 +92,13 @@ files:
89
92
  - spec/spec_helper.rb
90
93
  - spec/support/dummy_class.rb
91
94
  - spec/support/smart_property_matcher.rb
95
+ - spec/validations/ancestor_validation_spec.rb
96
+ - spec/writable_spec.rb
92
97
  homepage: ''
93
98
  licenses: []
94
- metadata: {}
95
- post_install_message:
99
+ metadata:
100
+ source_code_uri: https://github.com/t6d/smart_properties
101
+ post_install_message:
96
102
  rdoc_options: []
97
103
  require_paths:
98
104
  - lib
@@ -107,9 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
113
  - !ruby/object:Gem::Version
108
114
  version: '0'
109
115
  requirements: []
110
- rubyforge_project:
111
- rubygems_version: 2.6.11
112
- signing_key:
116
+ rubygems_version: 3.2.20
117
+ signing_key:
113
118
  specification_version: 4
114
119
  summary: SmartProperties – Ruby accessors on steroids
115
120
  test_files:
@@ -125,3 +130,5 @@ test_files:
125
130
  - spec/spec_helper.rb
126
131
  - spec/support/dummy_class.rb
127
132
  - spec/support/smart_property_matcher.rb
133
+ - spec/validations/ancestor_validation_spec.rb
134
+ - spec/writable_spec.rb