smart_properties 1.13.0 → 1.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fbf78f2b4f868f64a7e675336305c7f68579ec33
4
- data.tar.gz: 12eeb1777b492bfb56f4d3fe68e0b2fbea222d88
2
+ SHA256:
3
+ metadata.gz: 76b840210991bfec855a2e1159426635bc29e3994269bf305b5f0a8fe40fb0cf
4
+ data.tar.gz: f10d37b213920338b36b8a2da7bbecc1e8e0caf1d6eecf66084b6cbb4914a95e
5
5
  SHA512:
6
- metadata.gz: 67139c47fcc6da96c64d3d4489cfc16144f5cdb8772ece8b96e493a3aed6b127de5197003d7695f1d61365e7b2154a5983c2653c9b33e9529ac3d5f4702293b1
7
- data.tar.gz: 926f1575ffe3723a502ae0f2d427df880d638fafc47ff5870a19df87e81103cf435bf4f2931d583a08e9f7095e1ac41e2d2d7fcab6c54b628b728522f6777b64
6
+ metadata.gz: 30f8ed85cd3db3e2c17ddc30b63f85cba5541d26d8c8747a92529320531c11a58ae20435f08175061faedc7edf8f3bad2899ff32f2aa36465d4de2a78c8ca75e
7
+ data.tar.gz: fb602a52c408989d44c9ef0dd232f5891c6bc96f64444d077d498bb06ed1f63dfa671712abfe9ab0680214fb442ee4b029b6e0e518df01a35860e5fc6c072151
@@ -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 ADDED
@@ -0,0 +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`
@@ -220,7 +230,7 @@ class Person
220
230
  end
221
231
  ```
222
232
 
223
- #### Custom readers
233
+ #### Custom reader naming
224
234
 
225
235
  In Ruby, predicate methods by convention end with a `?`.
226
236
  This convention is violated in the example above, but can easily be fixed by supplying a custom `reader` name:
@@ -235,6 +245,22 @@ end
235
245
  To ensure backwards compatibility, boolean properties do not automatically change their reader name.
236
246
  It is thus your responsibility to configure the property properly.
237
247
 
248
+ #### Custom reader implementation
249
+
250
+ For convenience, it is possible to use the `super` method to access the original reader when overriding a reader.
251
+ This is recommended over direct access to the instance variable.
252
+
253
+ ```ruby
254
+ class Person
255
+ property :name
256
+ property! :address
257
+
258
+ def name
259
+ super || address.name
260
+ end
261
+ end
262
+ ```
263
+
238
264
  ### Constructor argument forwarding
239
265
 
240
266
  The `SmartProperties` initializer forwards anything to the super constructor
data/dev.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ name: smart-properties
3
+ up:
4
+ - ruby: 2.4.1
5
+ - bundler
6
+ commands:
7
+ test: bundle exec rake test
@@ -0,0 +1,34 @@
1
+ require 'benchmark'
2
+ require_relative '../lib/smart_properties'
3
+
4
+ class A
5
+ include SmartProperties
6
+ property :a
7
+ end
8
+
9
+ class B < A
10
+ property :b
11
+ end
12
+
13
+ class C < B
14
+ property :c
15
+ end
16
+
17
+ class A2
18
+ def initialize(**attrs)
19
+ attrs.each { |k, v| send("#{k}=", v) }
20
+ end
21
+ attr_accessor :a
22
+ end
23
+
24
+ class B2 < A2
25
+ attr_accessor :b
26
+ end
27
+
28
+ class C2 < B2
29
+ attr_accessor :c
30
+ end
31
+
32
+ puts Benchmark.measure { 1_000_000.times { C.new(a: 1, b: 2, c: 3) } }
33
+ # puts Benchmark.measure { 1_000_000.times { C2.new(a: 1, b: 2, c: 3) } }
34
+
@@ -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
 
@@ -153,9 +161,22 @@ module SmartProperties
153
161
 
154
162
  raise SmartProperties::InitializationError.new(self, missing_properties) unless missing_properties.empty?
155
163
  end
164
+
165
+ def [](name)
166
+ return if name.nil?
167
+ name = name.to_sym
168
+ reader = self.class.properties[name].reader
169
+ public_send(reader) if self.class.properties.key?(name)
170
+ end
171
+
172
+ def []=(name, value)
173
+ return if name.nil?
174
+ public_send(:"#{name.to_sym}=", value) if self.class.properties.key?(name)
175
+ end
156
176
  end
157
177
 
158
178
  require_relative 'smart_properties/property_collection'
159
179
  require_relative 'smart_properties/property'
160
180
  require_relative 'smart_properties/errors'
161
181
  require_relative 'smart_properties/version'
182
+ require_relative 'smart_properties/validations'
@@ -1,28 +1,14 @@
1
1
  module SmartProperties
2
2
  class Property
3
3
  MODULE_REFERENCE = :"@_smart_properties_method_scope"
4
-
5
- # Defines the two index methods #[] and #[]=. This module will be included
6
- # in the SmartProperties method scope.
7
- module IndexMethods
8
- def [](name)
9
- return if name.nil?
10
- name = name.to_sym
11
- reader = self.class.properties[name].reader
12
- public_send(reader) if self.class.properties.key?(name)
13
- end
14
-
15
- def []=(name, value)
16
- return if name.nil?
17
- public_send(:"#{name.to_sym}=", value) if self.class.properties.key?(name)
18
- end
19
- end
4
+ ALLOWED_DEFAULT_CLASSES = [Proc, Numeric, String, Range, TrueClass, FalseClass, NilClass, Symbol, Module].freeze
20
5
 
21
6
  attr_reader :name
22
7
  attr_reader :converter
23
8
  attr_reader :accepter
24
9
  attr_reader :reader
25
10
  attr_reader :instance_variable_name
11
+ attr_reader :writable
26
12
 
27
13
  def self.define(scope, name, options = {})
28
14
  new(name, options).tap { |p| p.define(scope) }
@@ -37,10 +23,16 @@ module SmartProperties
37
23
  @accepter = attrs.delete(:accepts)
38
24
  @required = attrs.delete(:required)
39
25
  @reader = attrs.delete(:reader)
26
+ @writable = attrs.delete(:writable)
40
27
  @reader ||= @name
41
28
 
42
29
  @instance_variable_name = :"@#{name}"
43
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
+
44
36
  unless attrs.empty?
45
37
  raise ConfigurationError, "SmartProperties do not support the following configuration options: #{attrs.keys.map { |m| m.to_s }.sort.join(', ')}."
46
38
  end
@@ -62,14 +54,25 @@ module SmartProperties
62
54
  !null_object?(get(scope))
63
55
  end
64
56
 
57
+ def writable?
58
+ return true if @writable.nil?
59
+ @writable
60
+ end
61
+
65
62
  def convert(scope, value)
66
63
  return value unless converter
67
64
  return value if null_object?(value)
68
- scope.instance_exec(value, &converter)
65
+
66
+ case converter
67
+ when Symbol
68
+ converter.to_proc.call(value)
69
+ else
70
+ scope.instance_exec(value, &converter)
71
+ end
69
72
  end
70
73
 
71
74
  def default(scope)
72
- @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default
75
+ @default.kind_of?(Proc) ? scope.instance_exec(&@default) : @default.dup
73
76
  end
74
77
 
75
78
  def accepts?(value, scope)
@@ -99,7 +102,7 @@ module SmartProperties
99
102
  if klass.instance_variable_defined?(MODULE_REFERENCE)
100
103
  klass.instance_variable_get(MODULE_REFERENCE)
101
104
  else
102
- m = Module.new { include IndexMethods }
105
+ m = Module.new
103
106
  klass.send(:include, m)
104
107
  klass.instance_variable_set(MODULE_REFERENCE, m)
105
108
  m
@@ -108,8 +111,11 @@ module SmartProperties
108
111
  scope.send(:define_method, reader) do
109
112
  property.get(self)
110
113
  end
111
- scope.send(:define_method, :"#{name}=") do |value|
112
- property.set(self, value)
114
+
115
+ if writable?
116
+ scope.send(:define_method, :"#{name}=") do |value|
117
+ property.set(self, value)
118
+ end
113
119
  end
114
120
  end
115
121
 
@@ -132,21 +138,38 @@ module SmartProperties
132
138
  scope.instance_variable_get(instance_variable_name)
133
139
  end
134
140
 
141
+ def to_h
142
+ {
143
+ accepter: @accepter,
144
+ converter: @converter,
145
+ default: @default,
146
+ instance_variable_name: @instance_variable_name,
147
+ name: @name,
148
+ reader: @reader,
149
+ required: @required
150
+ }
151
+ end
152
+
135
153
  private
136
154
 
137
155
  def null_object?(object)
138
- return true if object == nil
139
- return true if object.nil?
140
- false
156
+ object.nil?
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,18 @@ 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
+ parents.reduce(collection = new) do |previous, current|
15
+ current.properties.register(previous)
16
+ current.properties
17
17
  end
18
+
19
+ collection
18
20
  end
19
21
 
20
22
  def initialize
@@ -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'
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SmartProperties
2
- VERSION = "1.13.0"
2
+ VERSION = "1.16.0"
3
3
  end
@@ -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,12 +41,19 @@ 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
 
48
48
  it { is_expected.to have_smart_property(:title) }
49
49
 
50
+ it "should return a property's configuration with #to_h" do
51
+ expect(klass.properties.values.first.to_h).to match(
52
+ accepter: String, converter: :to_title, default: an_instance_of(Proc),
53
+ instance_variable_name: :@title, name: :title, reader: :title, required: true
54
+ )
55
+ end
56
+
50
57
  context "an instance of this class when initialized with no arguments" do
51
58
  subject(:instance) { klass.new }
52
59
 
@@ -157,4 +164,34 @@ RSpec.describe SmartProperties do
157
164
  end
158
165
  end
159
166
  end
167
+
168
+ context "when used to build a class that has a property called relation for an arbitrary Relation class" do
169
+ class Relation
170
+ attr_reader :equality_tested
171
+
172
+ def initialize
173
+ @equality_tested = false
174
+ end
175
+
176
+ def ==(_)
177
+ @equality_tested = true
178
+ false
179
+ end
180
+ end
181
+
182
+ subject(:klass) do
183
+ DummyClass.new do
184
+ property :relation, accepts: Relation, required: true
185
+ end
186
+ end
187
+
188
+ context 'with an instance of Relation' do
189
+ let(:relation) { Relation.new }
190
+
191
+ it 'should not execute #== on the object' do
192
+ instance = klass.new(relation: relation)
193
+ expect(relation.equality_tested).to eq(false)
194
+ end
195
+ end
196
+ end
160
197
  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,34 @@ 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
+ end
228
258
  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.0
4
+ version: 1.16.0
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: 2016-10-13 00:00:00.000000000 Z
11
+ date: 2021-08-18 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,16 +62,22 @@ executables: []
62
62
  extensions: []
63
63
  extra_rdoc_files: []
64
64
  files:
65
+ - ".github/workflows/testing.yml"
65
66
  - ".gitignore"
67
+ - ".ruby-version"
66
68
  - ".yardopts"
67
69
  - Gemfile
68
70
  - LICENSE
69
71
  - README.md
70
72
  - Rakefile
73
+ - dev.yml
74
+ - experiments/initialization_performance.rb
71
75
  - lib/smart_properties.rb
72
76
  - lib/smart_properties/errors.rb
73
77
  - lib/smart_properties/property.rb
74
78
  - lib/smart_properties/property_collection.rb
79
+ - lib/smart_properties/validations.rb
80
+ - lib/smart_properties/validations/ancestor.rb
75
81
  - lib/smart_properties/version.rb
76
82
  - smart_properties.gemspec
77
83
  - spec/acceptance_checking_spec.rb
@@ -86,10 +92,13 @@ files:
86
92
  - spec/spec_helper.rb
87
93
  - spec/support/dummy_class.rb
88
94
  - spec/support/smart_property_matcher.rb
95
+ - spec/validations/ancestor_validation_spec.rb
96
+ - spec/writable_spec.rb
89
97
  homepage: ''
90
98
  licenses: []
91
- metadata: {}
92
- post_install_message:
99
+ metadata:
100
+ source_code_uri: https://github.com/t6d/smart_properties
101
+ post_install_message:
93
102
  rdoc_options: []
94
103
  require_paths:
95
104
  - lib
@@ -104,9 +113,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
113
  - !ruby/object:Gem::Version
105
114
  version: '0'
106
115
  requirements: []
107
- rubyforge_project:
108
- rubygems_version: 2.5.1
109
- signing_key:
116
+ rubygems_version: 3.2.3
117
+ signing_key:
110
118
  specification_version: 4
111
119
  summary: SmartProperties – Ruby accessors on steroids
112
120
  test_files:
@@ -122,3 +130,5 @@ test_files:
122
130
  - spec/spec_helper.rb
123
131
  - spec/support/dummy_class.rb
124
132
  - spec/support/smart_property_matcher.rb
133
+ - spec/validations/ancestor_validation_spec.rb
134
+ - spec/writable_spec.rb