ryo.rb 0.4.4

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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/specs.yml +23 -0
  3. data/.gitignore +6 -0
  4. data/.gitlab-ci.yml +9 -0
  5. data/.rubocop.yml +56 -0
  6. data/.yardoc-template/default/fulldoc/html/css/0x1eef.css +15 -0
  7. data/.yardoc-template/default/layout/html/setup.rb +5 -0
  8. data/.yardoc-template/default/module/setup.rb +7 -0
  9. data/.yardopts +4 -0
  10. data/Gemfile +5 -0
  11. data/LICENSE.txt +22 -0
  12. data/README.md +373 -0
  13. data/Rakefile +3 -0
  14. data/lib/ryo/basic_object.rb +58 -0
  15. data/lib/ryo/builder.rb +106 -0
  16. data/lib/ryo/enumerable.rb +214 -0
  17. data/lib/ryo/function.rb +68 -0
  18. data/lib/ryo/keywords.rb +67 -0
  19. data/lib/ryo/lazy.rb +4 -0
  20. data/lib/ryo/object.rb +58 -0
  21. data/lib/ryo/reflect.rb +379 -0
  22. data/lib/ryo/version.rb +5 -0
  23. data/lib/ryo.rb +197 -0
  24. data/ryo.rb.gemspec +21 -0
  25. data/share/ryo.rb/examples/1.0_prototypes_point_object.rb +12 -0
  26. data/share/ryo.rb/examples/1.1_prototypes_ryo_fn.rb +14 -0
  27. data/share/ryo.rb/examples/2.0_iteration_each.rb +13 -0
  28. data/share/ryo.rb/examples/2.1_iteration_map.rb +16 -0
  29. data/share/ryo.rb/examples/2.2_iteration_ancestors.rb +13 -0
  30. data/share/ryo.rb/examples/3.0_recursion_ryo_from.rb +13 -0
  31. data/share/ryo.rb/examples/3.1_recursion_ryo_from_with_array.rb +19 -0
  32. data/share/ryo.rb/examples/3.2_recursion_ryo_from_with_openstruct.rb +14 -0
  33. data/share/ryo.rb/examples/4.0_basicobject_ryo_basicobject.rb +12 -0
  34. data/share/ryo.rb/examples/4.1_basicobject_ryo_basicobject_from.rb +13 -0
  35. data/share/ryo.rb/examples/5_collisions_resolution_strategy.rb +8 -0
  36. data/share/ryo.rb/examples/6_beyond_hash_objects.rb +20 -0
  37. data/share/ryo.rb/examples/7_ryo_lazy.rb +14 -0
  38. data/share/ryo.rb/examples/setup.rb +3 -0
  39. data/spec/readme_spec.rb +79 -0
  40. data/spec/ryo_basic_object_spec.rb +60 -0
  41. data/spec/ryo_enumerable_spec.rb +197 -0
  42. data/spec/ryo_keywords_spec.rb +86 -0
  43. data/spec/ryo_object_spec.rb +71 -0
  44. data/spec/ryo_prototypes_spec.rb +45 -0
  45. data/spec/ryo_reflect_spec.rb +175 -0
  46. data/spec/ryo_spec.rb +130 -0
  47. data/spec/setup.rb +5 -0
  48. metadata +173 -0
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+ require "ostruct"
6
+
7
+ point = Ryo.from(
8
+ OpenStruct.new(x: {to_i: 5}),
9
+ Ryo.from(y: {to_i: 10})
10
+ )
11
+ p [point.x.to_i, point.y.to_i]
12
+
13
+ ##
14
+ # [5, 10]
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ point_x = Ryo::BasicObject(x: 0)
7
+ point_y = Ryo::BasicObject({y: 0}, point_x)
8
+ point = Ryo::BasicObject({}, point_y)
9
+ p [point.x, point.y]
10
+
11
+ ##
12
+ # [0, 0]
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ point = Ryo::BasicObject.from({
7
+ x: {to_i: 2},
8
+ y: {to_i: 4}
9
+ })
10
+ p [point.x.to_i, point.y.to_i]
11
+
12
+ ##
13
+ # [2, 4]
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ ryo = Ryo::Object(then: 12)
7
+ p ryo.then # => 12
8
+ p ryo.then { 34 } # => 34
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ryo"
5
+
6
+ class Point
7
+ def initialize
8
+ @x = 5
9
+ @y = 10
10
+ end
11
+
12
+ def each_pair
13
+ yield("x", @x)
14
+ yield("y", @y)
15
+ end
16
+ end
17
+
18
+ option = Ryo(Point.new)
19
+ p option.x # => 5
20
+ p option.y # => 10
@@ -0,0 +1,14 @@
1
+ require_relative "setup"
2
+ require "ryo"
3
+
4
+ point_x = Ryo(x: Ryo.lazy { 5 })
5
+ point_y = Ryo({y: Ryo.lazy { 10 }}, point_x)
6
+ point = Ryo({sum: Ryo.lazy { x + y }}, point_y)
7
+ print "point.x = ", point.x, "\n"
8
+ print "point.y = ", point.y, "\n"
9
+ print "point.sum = ", point.sum, "\n"
10
+
11
+ ##
12
+ # point.x = 5
13
+ # point.y = 10
14
+ # point.sum = 15
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "test-cmd"
5
+
6
+ RSpec.describe "README.md examples" do
7
+ run_example = ->(file) do
8
+ Test::Cmd.cmd("ruby share/ryo.rb/examples/#{file}")
9
+ end
10
+
11
+ subject do
12
+ run_example.(file).stdout.chomp
13
+ end
14
+
15
+ context "when given 1.0_prototypes_point_object.rb" do
16
+ let(:file) { "1.0_prototypes_point_object.rb" }
17
+ it { is_expected.to eq("[5, 10]") }
18
+ end
19
+
20
+ context "when given 1.1_prototypes_ryo_fn.rb" do
21
+ let(:file) { "1.1_prototypes_ryo_fn.rb" }
22
+ it { is_expected.to eq("[10, 20]") }
23
+ end
24
+
25
+ context "when given 2.0_iteration_each.rb" do
26
+ let(:file) { "2.0_iteration_each.rb" }
27
+ it { is_expected.to eq(%(["x", 10]\n["y", 20])) }
28
+ end
29
+
30
+ context "when given 2.1_iteration_map.rb" do
31
+ let(:file) { "2.1_iteration_map.rb" }
32
+ it { is_expected.to eq("[4, 8]\n[4, 8]") }
33
+ end
34
+
35
+ context "when given 2.2_iteration_ancestors.rb" do
36
+ let(:file) { "2.2_iteration_ancestors.rb" }
37
+ it { is_expected.to eq("nil\nnil\n5\n5") }
38
+ end
39
+
40
+ context "when given 3.0_recursion_ryo_from.rb" do
41
+ let(:file) { "3.0_recursion_ryo_from.rb" }
42
+ it { is_expected.to eq("[0, 10]") }
43
+ end
44
+
45
+ context "when given 3.1_recursion_ryo_from_with_array.rb" do
46
+ let(:file) { "3.1_recursion_ryo_from_with_array.rb" }
47
+ it { is_expected.to eq(%(2\n"foobar"\n4)) }
48
+ end
49
+
50
+ context "when given 3.2_recursion_ryo_from_with_openstruct.rb" do
51
+ let(:file) { "3.2_recursion_ryo_from_with_openstruct.rb" }
52
+ it { is_expected.to eq("[5, 10]") }
53
+ end
54
+
55
+ context "when given 4.0_basicobject_ryo_basicobject.rb" do
56
+ let(:file) { "4.0_basicobject_ryo_basicobject.rb" }
57
+ it { is_expected.to eq("[0, 0]") }
58
+ end
59
+
60
+ context "when given 4.1_basicobject_ryo_basicobject_from.rb" do
61
+ let(:file) { "4.1_basicobject_ryo_basicobject_from.rb" }
62
+ it { is_expected.to eq("[2, 4]") }
63
+ end
64
+
65
+ context "when given 5_collisions_resolution_strategy.rb" do
66
+ let(:file) { "5_collisions_resolution_strategy.rb" }
67
+ it { is_expected.to eq("12\n34") }
68
+ end
69
+
70
+ context "when given 6_beyond_hash_objects.rb" do
71
+ let(:file) { "6_beyond_hash_objects.rb" }
72
+ it { is_expected.to eq("5\n10") }
73
+ end
74
+
75
+ context "when given 7_ryo_lazy.rb" do
76
+ let(:file) { "7_ryo_lazy.rb" }
77
+ it { is_expected.to eq("point.x = 5\npoint.y = 10\npoint.sum = 15") }
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+ require "ostruct"
5
+
6
+ RSpec.describe Ryo::BasicObject do
7
+ describe "Ryo::BasicObject()" do
8
+ context "when given a Hash" do
9
+ subject { [point.x, point.y] }
10
+ let(:point) { Ryo::BasicObject(x: 0, y: 0) }
11
+ it { is_expected.to eq([0, 0]) }
12
+ end
13
+
14
+ context "when given an OpenStruct" do
15
+ subject { [point.x, point.y] }
16
+ let(:point) { Ryo::BasicObject(OpenStruct.new(x: 0, y: 0)) }
17
+ it { is_expected.to eq([0, 0]) }
18
+
19
+ context "when verifying the object is a Ryo object" do
20
+ subject { Ryo === point }
21
+ it { is_expected.to be(true) }
22
+ end
23
+ end
24
+ end
25
+
26
+ describe ".from" do
27
+ context "when given an instance of Ryo::BasicObject" do
28
+ subject { Ryo.from(point) }
29
+ let(:point) { Ryo::BasicObject(x: 5, y: 10) }
30
+ it { is_expected.to eq(point) }
31
+ end
32
+
33
+ context "when given nested Hash objects" do
34
+ subject { point.x.to_i }
35
+ let(:point) { Ryo::BasicObject.from({x: {to_i: 4}}) }
36
+ it { is_expected.to eq(4) }
37
+ end
38
+
39
+ context "when given an Array that contains nested Hash objects" do
40
+ subject { points[0].x.to_i }
41
+ let(:points) { Ryo::BasicObject.from([{x: {to_i: 4}}]) }
42
+ it { is_expected.to eq(4) }
43
+ end
44
+
45
+ context "with a prototype" do
46
+ let(:point_a) { Ryo::BasicObject.from(x: {to_i: 0}) }
47
+ let(:point_b) { Ryo::BasicObject.from({y: {to_i: 2}}, point_a) }
48
+
49
+ context "when traversing to the prototype (point_a)" do
50
+ subject { point_b.x.to_i }
51
+ it { is_expected.to eq(0) }
52
+ end
53
+
54
+ context "when verifying a nested Hash doesn't inherit the prototype (point_a)" do
55
+ subject { point_b.y.x }
56
+ it { is_expected.to eq(nil) }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,197 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe Ryo::Enumerable do
6
+ describe ".each" do
7
+ context "when verifying each traverses through the prototype chain" do
8
+ subject { Ryo.each(point_c).map { [_1, _2] } }
9
+ let(:point_a) { Ryo(x: 0) }
10
+ let(:point_b) { Ryo({y: 5}, point_a) }
11
+ let(:point_c) { Ryo({}, point_b) }
12
+ it { is_expected.to eq([["y", 5], ["x", 0]]) }
13
+ end
14
+ end
15
+
16
+ describe ".map" do
17
+ subject(:point_c) { Ryo.map(point_b) { _2 * 2 } }
18
+ let(:point_a) { Ryo::BasicObject(x: 4, y: 4) }
19
+ let(:point_b) { Ryo::BasicObject({x: 2, y: 2}, point_a) }
20
+
21
+ context "when verifying the map operation" do
22
+ it { is_expected.to eq({x: 4, y: 4}) }
23
+ end
24
+
25
+ context "when verifying the map operation on the prototype" do
26
+ subject { point_a }
27
+ before { Ryo.map!(point_b) { _2 * 2 } }
28
+ it { is_expected.to eq({x: 8, y: 8}) }
29
+ end
30
+
31
+ context "when verifying the map operation returns a new object" do
32
+ subject { Ryo.kernel(:equal?).bind_call(point_b, point_c) }
33
+ it { is_expected.to be(false) }
34
+ end
35
+ end
36
+
37
+ describe ".select" do
38
+ context "with prototype chain traversal" do
39
+ subject { Ryo.select(point_b) { _1 == "y" and _2 == 4 } }
40
+ let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
41
+ let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
42
+
43
+ context "when verifying the filter operation" do
44
+ it { is_expected.to eq(y: 4) }
45
+ end
46
+
47
+ context "when verifying the filter operation on the prototype" do
48
+ subject { point_a.y }
49
+ before { Ryo.select!(point_b) { _1 == "x" } }
50
+ it { is_expected.to eq(nil) }
51
+ end
52
+ end
53
+ end
54
+
55
+ describe ".reject" do
56
+ context "with prototype chain traversal" do
57
+ subject { Ryo.reject(point_b) { _1 == "x" } }
58
+ let(:point_a) { Ryo::BasicObject(x: 1, y: 2) }
59
+ let(:point_b) { Ryo::BasicObject({x: 3, y: 4}, point_a) }
60
+
61
+ context "when verifying the filter operation" do
62
+ it { is_expected.to eq(y: 4) }
63
+ end
64
+
65
+ context "when verifying the filter operation on the prototype" do
66
+ subject { point_a.y }
67
+ before { Ryo.reject!(point_b) { _1 == "y" } }
68
+ it { is_expected.to eq(nil) }
69
+ end
70
+ end
71
+ end
72
+
73
+ describe ".any?" do
74
+ let(:point_a) { Ryo::BasicObject(y: 10) }
75
+ let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
76
+
77
+ context "when an iteration returns a truthy value" do
78
+ subject { Ryo.any?(point_b) { _2 > 5 } }
79
+ it { is_expected.to be(true) }
80
+ end
81
+
82
+ context "when an iteration fails to return a truthy value" do
83
+ subject { Ryo.any?(point_b) { _2 > 20 } }
84
+ it { is_expected.to be(false) }
85
+ end
86
+ end
87
+
88
+ describe ".all?" do
89
+ let(:point_a) { Ryo::BasicObject(y: 10) }
90
+ let(:point_b) { Ryo::BasicObject({x: 5}, point_a) }
91
+ let(:point_c) { Ryo::BasicObject({z: 0}, point_b) }
92
+
93
+ context "when every iteration returns a truthy value" do
94
+ subject { Ryo.all?(point_c) { _2 < 11 } }
95
+ it { is_expected.to be(true) }
96
+ end
97
+
98
+ context "when an iteration fails to return a truthy value" do
99
+ subject { Ryo.all?(point_c) { _2 < 5 } }
100
+ it { is_expected.to be(false) }
101
+ end
102
+ end
103
+
104
+ describe ".find" do
105
+ let(:point_a) { Ryo::BasicObject(x: 5) }
106
+ let(:point_b) { Ryo::BasicObject({y: 10}, point_a) }
107
+ let(:point_c) { Ryo::BasicObject({z: 15}, point_b) }
108
+
109
+ context "when an iteration yields true on point_a" do
110
+ subject { Ryo.find(point_c) { _2 == 5 } }
111
+ it { is_expected.to be(point_a) }
112
+ end
113
+
114
+ context "when an iteration yields true on point_b" do
115
+ subject { Ryo.find(point_c) { _2 == 10 } }
116
+ it { is_expected.to be(point_b) }
117
+ end
118
+
119
+ context "when an iteration yields true on point_c" do
120
+ subject { Ryo.find(point_c) { _2 == 15 } }
121
+ it { is_expected.to be(point_c) }
122
+ end
123
+
124
+ context "when an iteration never yields true" do
125
+ subject { Ryo.find(point_c) { _2 == 20 } }
126
+ it { is_expected.to eq(nil) }
127
+ end
128
+
129
+ context "when ancestors is set to zero" do
130
+ context "when the condition matches for point_a" do
131
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 5 } }
132
+ it { is_expected.to be_nil }
133
+ end
134
+
135
+ context "when the condition matches for point_b" do
136
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 10 } }
137
+ it { is_expected.to be_nil }
138
+ end
139
+
140
+ context "when the condition matches for point_c" do
141
+ subject { Ryo.find(point_c, ancestors: 0) { _2 == 15 } }
142
+ it { is_expected.to be(point_c) }
143
+ end
144
+ end
145
+
146
+ context "when ancestors is set to one" do
147
+ context "when the condition matches for point_a" do
148
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 5 } }
149
+ it { is_expected.to be_nil }
150
+ end
151
+
152
+ context "when the condition matches for point_b" do
153
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 10 } }
154
+ it { is_expected.to be(point_b) }
155
+ end
156
+
157
+ context "when the condition matches for point_c" do
158
+ subject { Ryo.find(point_c, ancestors: 1) { _2 == 15 } }
159
+ it { is_expected.to be(point_c) }
160
+ end
161
+ end
162
+
163
+ context "when ancestors is set to two" do
164
+ context "when the condition matches for point_a" do
165
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 5 } }
166
+ it { is_expected.to be(point_a) }
167
+ end
168
+
169
+ context "when the condition matches for point_b" do
170
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 10 } }
171
+ it { is_expected.to be(point_b) }
172
+ end
173
+
174
+ context "when the condition matches for point_c" do
175
+ subject { Ryo.find(point_c, ancestors: 2) { _2 == 15 } }
176
+ it { is_expected.to be(point_c) }
177
+ end
178
+ end
179
+
180
+ context "when ancestors is set to three (or higher)" do
181
+ context "when the condition matches for point_a" do
182
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 5 } }
183
+ it { is_expected.to be(point_a) }
184
+ end
185
+
186
+ context "when the condition matches for point_b" do
187
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 10 } }
188
+ it { is_expected.to be(point_b) }
189
+ end
190
+
191
+ context "when the condition matches for point_c" do
192
+ subject { Ryo.find(point_c, ancestors: 3) { _2 == 15 } }
193
+ it { is_expected.to be(point_c) }
194
+ end
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe Ryo::Keywords do
6
+ describe ".function" do
7
+ let(:point) { Ryo(move: Ryo.fn { |x, y| [x, y] }) }
8
+
9
+ context "when the function requires argument(s)" do
10
+ context "when the required argument is not given" do
11
+ subject { point.move.() }
12
+ it { expect { is_expected }.to raise_error(ArgumentError) }
13
+ end
14
+
15
+ context "when the required argument is given" do
16
+ subject { point.move.(30, 50) }
17
+ it { is_expected.to eq([30, 50]) }
18
+ end
19
+ end
20
+
21
+ context "when the function receives a block" do
22
+ subject { point.move.() { "block" } }
23
+ let(:point) { Ryo(move: Ryo.fn { |&b| b.() }) }
24
+ it { is_expected.to eq("block") }
25
+ end
26
+ end
27
+
28
+ describe ".delete" do
29
+ let(:point_a) { Ryo(x: 0) }
30
+ let(:point_b) { Ryo({x: 1}, point_a) }
31
+
32
+ context "with no prototype" do
33
+ context "when a property is deleted" do
34
+ subject { point_a.x }
35
+ before { Ryo.delete(point_a, "x") }
36
+ it { is_expected.to be_nil }
37
+ end
38
+ end
39
+
40
+ context "with a prototype" do
41
+ context "when a property is deleted from point_a" do
42
+ subject { point_b.x }
43
+ before { Ryo.delete(point_a, "x") }
44
+ it { is_expected.to eq(1) }
45
+ end
46
+
47
+ context "when a property is deleted from point_b" do
48
+ subject { point_b.x }
49
+ before { Ryo.delete(point_b, "x") }
50
+ it { is_expected.to eq(0) }
51
+ end
52
+
53
+ context "when a property is deleted from both point_a / point_b" do
54
+ subject { point_b.x }
55
+ before { [point_a, point_b].each { Ryo.delete(_1, "x") } }
56
+ it { is_expected.to be(nil) }
57
+ end
58
+ end
59
+ end
60
+
61
+ describe ".in?" do
62
+ let(:point_a) { Ryo(x: 0) }
63
+ let(:point_b) { Ryo({y: 1}, point_a) }
64
+ let(:point_c) { Ryo({z: 2}, point_b) }
65
+
66
+ context "when given 'x' as a property name" do
67
+ subject { Ryo.in?(point_c, "x") }
68
+ it { is_expected.to be(true) }
69
+ end
70
+
71
+ context "when given 'y' as a property name" do
72
+ subject { Ryo.in?(point_c, "y") }
73
+ it { is_expected.to be(true) }
74
+ end
75
+
76
+ context "when given 'z' as a property name" do
77
+ subject { Ryo.in?(point_c, "z") }
78
+ it { is_expected.to be(true) }
79
+ end
80
+
81
+ context "when given 'w' as a property name" do
82
+ subject { Ryo.in?(point_c, "w") }
83
+ it { is_expected.to be(false) }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe "Ryo objects" do
6
+ let(:car) { Ryo(name: "Car") }
7
+
8
+ describe "#respond_to?" do
9
+ context "when a property is defined" do
10
+ subject { car.respond_to?(:name) }
11
+ it { is_expected.to be(true) }
12
+ end
13
+
14
+ context "when a property is not defined" do
15
+ subject { car.respond_to?(:foobar) }
16
+ it { is_expected.to be(true) }
17
+ end
18
+ end
19
+
20
+ describe "#method_missing" do
21
+ context "when a property doesn't exist" do
22
+ subject { car.foobar }
23
+ it { is_expected.to eq(nil) }
24
+ end
25
+ end
26
+
27
+ describe "#eql?" do
28
+ context "when two objects are equal" do
29
+ subject { car == car_2 }
30
+ let(:car_2) { Ryo(name: "Car") }
31
+ it { is_expected.to be(true) }
32
+ end
33
+
34
+ context "when an object and a Hash are equal" do
35
+ subject { car == {"name" => "Car"} }
36
+ it { is_expected.to be(true) }
37
+ end
38
+
39
+ context "when an object and symbol-key Hash are equal" do
40
+ subject { car == {name: "Car"} }
41
+ it { is_expected.to be(true) }
42
+ end
43
+
44
+ context "when an object and nested symbol-key Hash are equal" do
45
+ subject { car == {name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}} }
46
+ let(:car) { Ryo.from(name: "Car", wheels: {quantity: 4, weight: {lbs: "50"}}) }
47
+ it { is_expected.to be(true) }
48
+ end
49
+
50
+ context "when an object is compared against nil" do
51
+ subject { car == nil }
52
+ it { is_expected.to be(false) }
53
+ end
54
+ end
55
+
56
+ describe "when a property overshadows a method" do
57
+ let(:car) do
58
+ Ryo(tap: "property")
59
+ end
60
+
61
+ context "when a block is not given" do
62
+ subject { car.tap }
63
+ it { is_expected.to eq("property") }
64
+ end
65
+
66
+ context "when a block is given" do
67
+ subject { car.tap {} }
68
+ it { is_expected.to eq(car) }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "setup"
4
+
5
+ RSpec.describe "Prototypes" do
6
+ context "when there is one prototype" do
7
+ let(:root) { Ryo(name: "root") }
8
+ let(:node) { Ryo({}, root) }
9
+
10
+ context "when traversing to a property on the root prototype" do
11
+ subject { node.name }
12
+ it { is_expected.to eq("root") }
13
+ end
14
+
15
+ context "when a property is deleted from the root prototype" do
16
+ subject { node.name }
17
+ before { Ryo.delete(root, "name") }
18
+ it { is_expected.to eq(nil) }
19
+ end
20
+ end
21
+
22
+ context "when there are two prototypes" do
23
+ let(:root) { Ryo(name: "root") }
24
+ let(:node_1) { Ryo({}, root) }
25
+ let(:node_2) { Ryo({}, node_1) }
26
+
27
+ context "when traversing to a property on the root prototype" do
28
+ subject { node_2.name }
29
+ it { is_expected.to eq("root") }
30
+ end
31
+
32
+ context "when traversing to a property on the middle prototype" do
33
+ subject { node_2.name }
34
+ let(:node_1) { Ryo({name: "Node 1"}, root) }
35
+ it { is_expected.to eq("Node 1") }
36
+ end
37
+
38
+ context "when a property is deleted from the middle prototype" do
39
+ subject { node_2.name }
40
+ let(:node_1) { Ryo({name: "Node 1"}, root) }
41
+ before { Ryo.delete(node_1, "name") }
42
+ it { is_expected.to eq("root") }
43
+ end
44
+ end
45
+ end