astruct 2.11.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6990e238c22d0d907f210e88381630a930b20355
4
+ data.tar.gz: 028d8e80cc9b222c3383c511f514426a3d888eea
5
+ SHA512:
6
+ metadata.gz: 64a93ec3749af729bd2d2bce28a225f22ec46e8ad415e4ba8004f69ad82dd81a0cf9b925a6040b76fb316a4bbf6e954542bce07540503d5ce6f9a29289e418fd
7
+ data.tar.gz: 5708d234e2e1cea7f644dc7be147ebc267d3c931aacc5a93b7a4386a2cb588ed1949eb4acfaf0d360871843dbe1a0eefba09f4ba6d258fd897be26639ff43e67
@@ -1,6 +1,5 @@
1
- require_relative 'astruct/module'
1
+ require_relative "astruct/behavior"
2
2
 
3
- #
4
3
  # = astruct.rb: AltStruct implementation
5
4
  #
6
5
  # Author:: Kurtis Rainbolt-Greene
@@ -10,85 +9,11 @@ require_relative 'astruct/module'
10
9
  # create hash-like classes. Allowing you to create an object that can
11
10
  # dynamically accept accessors and behaves very much like a Hash.
12
11
  #
13
-
14
- #
15
- # An AltStruct is a data structure, similar to a Hash, that allows the
16
- # definition of arbitrary attributes with their accompanying values. This is
17
- # accomplished by using Ruby's metaprogramming to define methods on the class
18
- # itself.
19
- #
20
-
21
- #
22
- # == Examples:
23
- #
24
- # require 'astruct'
25
- #
26
- # class Profile < AltStruct
27
- #
28
- # end
29
- #
30
- # person = Profile.new name: "John Smith"
31
- # person.age = 70
32
- #
33
- # puts person.name # => "John Smith"
34
- # puts person.age # => 70
35
- # puts person.dump # => { :name => "John Smith", :age => 70 }
36
- #
37
-
38
- #
39
- # An AltStruct employs a Hash internally to store the methods and values and
40
- # can even be initialized with one:
41
- #
42
- # australia = AltStruct.new country: "Australia", population: 20_000_000
43
- # puts australia.inspect # => <AltStruct country="Australia", population=20000000>
44
- #
45
-
46
- #
47
- # Hash keys with spaces or characters that would normally not be able to use for
48
- # method calls (e.g. ()[]*) will not be immediately available on the
49
- # AltStruct object as a method for retrieval or assignment, but can be still be
50
- # reached through the Object#send method.
51
- #
52
- # measurements = AltStruct.new "length (in inches)" => 24
53
- # measurements.send "length (in inches)" # => 24
54
- #
55
- # data_point = AltStruct.new :queued? => true
56
- # data_point.queued? # => true
57
- # data_point.send "queued?=", false
58
- # data_point.queued? # => false
59
- #
60
-
61
- #
62
- # Removing the presence of a method requires the execution the delete_field
63
- # or delete (like a hash) method as setting the property value to +nil+
64
- # will not remove the method.
65
- #
66
- # first_pet = AltStruct.new :name => 'Rowdy', :owner => 'John Smith'
67
- # first_pet.owner = nil
68
- # second_pet = AltStruct.new :name => 'Rowdy'
69
- #
70
- # first_pet == second_pet # -> false
71
- #
72
- # first_pet.delete_field(:owner)
73
- # first_pet == second_pet # -> true
74
- #
75
-
76
- #
77
- # == Implementation:
78
- #
79
- # An AltStruct utilizes Ruby's method lookup structure to and find and define
80
- # the necessary methods for properties. This is accomplished through the method
81
- # method_missing and define_method.
82
- #
83
-
84
- #
85
- # This should be a consideration if there is a concern about the performance of
86
- # the objects that are created, as there is much more overhead in the setting
87
- # of these properties compared to using a Hash or a Struct.
88
- #
89
12
  class AltStruct
90
- # We include all of the AltStruct::M Module in order to give AltStruct
13
+ # We include all of the AltStruct::Behavior Module in order to give AltStruct
91
14
  # the same behavior as OpenStruct. It's better, however, to simply
92
- # include AltStruct::M into your own class.
93
- include AltStruct::M
15
+ # include AltStruct::Behavior into your own class.
16
+ include AltStruct::Behavior
94
17
  end
18
+
19
+ require_relative "astruct/version"
@@ -0,0 +1,251 @@
1
+ require "set"
2
+
3
+ class AltStruct
4
+ # An AltStruct is a data structure, similar to a Hash, that allows the
5
+ # definition of arbitrary attributes with their accompanying values. This is
6
+ # accomplished by using Ruby's meta-programming to define methods on the
7
+ # class itself.
8
+ #
9
+ #
10
+ # == Examples:
11
+ #
12
+ # require 'astruct'
13
+ #
14
+ # class Profile < AltStruct
15
+ #
16
+ # end
17
+ #
18
+ # person = Profile.new name: "John Smith"
19
+ # person.age = 70
20
+ #
21
+ # puts person.name # => "John Smith"
22
+ # puts person.age # => 70
23
+ # puts person.dump # => { :name => "John Smith", :age => 70 }
24
+ #
25
+ # An AltStruct employs a Hash internally to store the methods and values and
26
+ # can even be initialized with one:
27
+ #
28
+ # australia = AltStruct.new(
29
+ # country: "Australia",
30
+ # population: 20_000_000
31
+ # )
32
+ # puts australia.inspect
33
+ # # => <AltStruct country="Australia", population=20000000>
34
+ #
35
+ # Hash keys with spaces or characters that would normally not be able to use
36
+ # for method calls (e.g. ()[]*) will not be immediately available on the
37
+ # AltStruct object as a method for retrieval or assignment, but can be still
38
+ # be reached through the `Object#send` method.
39
+ #
40
+ # measurements = AltStruct.new "length (in inches)" => 24
41
+ # measurements.send "length (in inches)" # => 24
42
+ #
43
+ # data_point = AltStruct.new :queued? => true
44
+ # data_point.queued? # => true
45
+ # data_point.send "queued?=", false
46
+ # data_point.queued? # => false
47
+ #
48
+ # Removing the presence of a method requires the execution the delete_field
49
+ # or delete (like a hash) method as setting the property value to +nil+
50
+ # will not remove the method.
51
+ #
52
+ # first_pet = AltStruct.new :name => 'Rowdy', :owner => 'John Smith'
53
+ # first_pet.owner = nil
54
+ # second_pet = AltStruct.new :name => 'Rowdy'
55
+ #
56
+ # first_pet == second_pet # -> false
57
+ #
58
+ # first_pet.delete_field(:owner)
59
+ # first_pet == second_pet # -> true
60
+ #
61
+ #
62
+ # == Implementation:
63
+ #
64
+ # An AltStruct utilizes Ruby's method lookup structure to and find and define
65
+ # the necessary methods for properties. This is accomplished through the
66
+ # method `method_missing` and `define_singleton_method`.
67
+ #
68
+ # This should be a consideration if there is a concern about the performance
69
+ # of the objects that are created, as there is much more overhead in the
70
+ # setting of these properties compared to using a Hash or a Struct.
71
+ module Behavior
72
+ THREAD_KEY = :__as_ids__ # :nodoc:
73
+ NESTED_INSPECT = "...".freeze
74
+ INSPECT_DELIMITER = ", ".freeze
75
+ WRAP_PATTERN = /__/.freeze
76
+ UNSETABLE_PATTERN = /\@|\[|\]|\=\=|\~|\>|\<|\!\=/.freeze
77
+ SUFFIX_PATTERN = /(\?|\!)$/.freeze
78
+
79
+ # We want to give easy access to the table
80
+ attr_reader :table
81
+
82
+ # We want to automatically wrap important Ruby object methods
83
+ Object.instance_methods.each do |meth|
84
+ case meth
85
+
86
+ # Don't bother with already wrapped methods
87
+ when WRAP_PATTERN then next
88
+
89
+ # Skip methods that can't be set anyways
90
+ when UNSETABLE_PATTERN then next
91
+
92
+ # Get around Ruby's stupid method signature problems with ? and !
93
+ # suffixes
94
+ when SUFFIX_PATTERN then alias_method("__#{meth[0...-1]}__#{meth[-1]}".to_sym, meth)
95
+
96
+ # Finally, wrap regular methods
97
+ else alias_method("__#{meth}__".to_sym, meth)
98
+ end
99
+ end
100
+
101
+ # Create a new field for each of the key/value pairs passed.
102
+ # By default the resulting OpenStruct object will have no
103
+ # attributes. If no pairs are passed avoid any work.
104
+ #
105
+ # require "astruct"
106
+ # hash = { "country" => "Australia", :population => 20_000_000 }
107
+ # data = AltStruct.new hash
108
+ #
109
+ # p data # => <AltStruct country="Australia" population=20000000>
110
+ #
111
+ # If you happen to be inheriting then you can define your own
112
+ # `@table` ivar before the `super()` call. AltStruct will respect
113
+ # your `@table`.
114
+ #
115
+ def initialize(pairs = {})
116
+ @table ||= {}
117
+ __iterate_set_over__(pairs) unless pairs.empty?
118
+ end
119
+
120
+ # This is the `load()` method, which works like initialize in that it
121
+ # will create new fields for each pair passed. It mimics the behavior of a
122
+ # Hash#merge.
123
+ def __load__(pairs)
124
+ __iterate_set_over__(pairs) unless pairs.empty?
125
+ end
126
+ alias_method :marshal_load, :__load__
127
+ alias_method :load, :__load__
128
+ alias_method :merge, :__load__
129
+
130
+ # This is the `load!()` method, which works like Hash#merge!
131
+ # See: `AltStruct#load()`
132
+ def __load__!(pairs)
133
+ __iterate_set_over__(pairs, true)
134
+ end
135
+ alias_method :marshal_load!, :__load__!
136
+ alias_method :load!, :__load__!
137
+ alias_method :merge!, :__load__!
138
+
139
+ # The `dump()` takes the table and out puts in it's natural hash
140
+ # format. In addition you can pass along a specific set of keys to
141
+ # dump.
142
+ def __dump__(*keys)
143
+ if keys.empty? then @table else __dump_specific__(keys) end
144
+ end
145
+ alias_method :marshal_dump, :__dump__
146
+ alias_method :dump, :__dump__
147
+ alias_method :to_hash, :__dump__
148
+
149
+ def __inspect__
150
+ "#<#{__class__}#{__dump_inspect__}>"
151
+ end
152
+ alias_method :inspect, :__inspect__
153
+ alias_method :to_sym, :__inspect__
154
+
155
+ # The `delete()` method removes a key/value pair on the @table
156
+ # and on the singleton class. It also mimics the Hash#delete method.
157
+ def __delete__(key)
158
+ __singleton_class__.send(:remove_method, key)
159
+ __singleton_class__.send(:remove_method, "#{key}=")
160
+ @table.delete(key.to_sym)
161
+ end
162
+ alias_method :delete_field, :__delete__
163
+ alias_method :delete, :__delete__
164
+
165
+ # The `method_missing()` method catches all non-tabled method calls.
166
+ # The AltStruct object will return two specific errors depending on
167
+ # the call.
168
+ def method_missing(method, *arguments)
169
+ name = method.to_s
170
+ if name.split("").last == "=" && arguments.size == 1
171
+ __define_field__(name.chomp!("="), arguments.first)
172
+ else
173
+ if name.split.last != "="
174
+ super
175
+ else arguments.size > 1
176
+ raise(ArgumentError,"wrong number of arguments (#{arguments.size} for 1)")
177
+ end
178
+ end
179
+ end
180
+
181
+ def ==(other)
182
+ if other.respond_to?(:table)
183
+ table == other.table
184
+ else
185
+ false
186
+ end
187
+ end
188
+
189
+ def freeze
190
+ super
191
+ @table.freeze
192
+ end
193
+ alias_method :__freeze__, :freeze
194
+ alias_method :__frozen?, :frozen?
195
+
196
+ private
197
+
198
+ def __dump_inspect__
199
+ Thread.current[THREAD_KEY] ||= Set.new
200
+
201
+ if __dump__.any? then " #{__dump_subinspect__}" else "" end.tap do
202
+ __thread_ids__.delete(__object_id__)
203
+ end
204
+ end
205
+
206
+ def __dump_subinspect__
207
+ if __thread_ids__.add?(__object_id__)
208
+ __dump_string__.join(INSPECT_DELIMITER)
209
+ else
210
+ NESTED_INSPECT
211
+ end
212
+ end
213
+
214
+ def __thread_ids__
215
+ Thread.current[THREAD_KEY]
216
+ end
217
+
218
+ def __define_field__(key, value)
219
+ __define_accessor__(key)
220
+ __set_table__(key, value)
221
+ end
222
+
223
+ def __define_accessor__(key)
224
+ singleton_class.send(:define_method, key) { @table[key] }
225
+ singleton_class.send(:define_method, "#{key}=") { |v| @table[key] = v }
226
+ end
227
+
228
+ def __set_table__(key, value)
229
+ @table.merge!(key => value) unless key.nil?
230
+ end
231
+
232
+ def __dump_specific__(keys)
233
+ @table.keep_if { |key| keys.include?(key.to_sym) }
234
+ end
235
+
236
+ def __dump_string__
237
+ __dump__.map { |key, value| "#{key}=#{value.inspect}" }
238
+ end
239
+
240
+ def __iterate_set_over__(pairs, force = false)
241
+ pairs.each do |key, value|
242
+ if force && respond_to?(key)
243
+ __set_table__(key, value)
244
+ else
245
+ __define_accessor__(key)
246
+ __set_table__(key, value)
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end
@@ -1,4 +1,3 @@
1
1
  class AltStruct
2
- # This is the AltStruct version
3
- VERSION = "2.11.0"
2
+ VERSION = "3.0.0"
4
3
  end
@@ -0,0 +1,10 @@
1
+ require "spec_helper"
2
+
3
+ describe AltStruct::Behavior do
4
+ let(:pairs) { { a: 1, b: 2, c: 3} }
5
+ let(:astruct) { ExampleAltStruct.new(pairs) }
6
+
7
+ it "reponds to each key as a method" do
8
+ expect(astruct).to have_attributes(pairs)
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe AltStruct::VERSION do
4
+ it "should be a string" do
5
+ expect(AltStruct::VERSION).to be_kind_of(String)
6
+ end
7
+ end
@@ -0,0 +1,13 @@
1
+ require "spec_helper"
2
+
3
+ RSpec.describe AltStruct do
4
+ let(:astruct) { described_class.new }
5
+
6
+ it "should behave like AltStruct::Behavior" do
7
+ expect(astruct).to respond_to(*AltStruct::Behavior.instance_methods)
8
+ end
9
+
10
+ it "should have aliases for standard methods" do
11
+ expect(astruct).to respond_to(:__object_id__)
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ require "codeclimate-test-reporter"
2
+ require "pry"
3
+ require "rspec"
4
+ require "astruct"
5
+
6
+ RSpec.configure do |let|
7
+ let.before("suite") do
8
+ CodeClimate::TestReporter.start
9
+ end
10
+
11
+ # Exit the spec after the first failure
12
+ let.fail_fast = true
13
+
14
+ # Only run a specific file, using the ENV variable
15
+ # Example: FILE=spec/blankgem/version_spec.rb bundle exec rake spec
16
+ let.pattern = ENV["FILE"]
17
+
18
+ # Show the slowest examples in the suite
19
+ let.profile_examples = true
20
+
21
+ # Colorize the output
22
+ let.color = true
23
+
24
+ # Output as a document string
25
+ let.default_formatter = "doc"
26
+ end
27
+
28
+ class ExampleAltStruct
29
+ include AltStruct::Behavior
30
+ end
@@ -1,10 +1,10 @@
1
- require 'helper'
2
- require 'minitest/autorun'
1
+ require "test_helper"
2
+ require "minitest/autorun"
3
3
 
4
4
  class TestAltStruct < MiniTest::Unit::TestCase
5
5
  def setup
6
6
  @empty = AltStruct.new
7
- @example = AltStruct.new name: "Kurtis", age: 24
7
+ @example = AltStruct.new(name: "Kurtis", age: 24)
8
8
  end
9
9
 
10
10
  def test_equality_with_two_empty_astructs
@@ -39,9 +39,9 @@ class TestAltStruct < MiniTest::Unit::TestCase
39
39
  end
40
40
 
41
41
  def test_inspect_with_sub_struct_duplicate
42
- @empty.struct2 = AltStruct.new
43
- @empty.struct2.struct3 = @empty
44
- expected = '#<AltStruct struct2=#<AltStruct struct3=#<AltStruct ...>>>'
42
+ @empty.substruct = AltStruct.new
43
+ @empty.substruct.subsubstruct = @empty
44
+ expected = "#<AltStruct substruct=#<AltStruct subsubstruct=#<AltStruct ...>>>"
45
45
  actual = @empty.inspect
46
46
  assert_equal expected, actual
47
47
  end
@@ -50,7 +50,7 @@ class TestAltStruct < MiniTest::Unit::TestCase
50
50
  @example.friends = AltStruct.new name: "Jason", age: 24
51
51
  @example.friends.friends = AltStruct.new name: "John", age: 15
52
52
  @example.friends.friends.friends = AltStruct.new name: "Ally", age: 32
53
- expected = '#<AltStruct name="Kurtis", age=24, friends=#<AltStruct name="Jason", age=24, friends=#<AltStruct name="John", age=15, friends=#<AltStruct name="Ally", age=32>>>>'
53
+ expected = "#<AltStruct name=\"Kurtis\", age=24, friends=#<AltStruct name=\"Jason\", age=24, friends=#<AltStruct name=\"John\", age=15, friends=#<AltStruct name=\"Ally\", age=32>>>>"
54
54
  actual = @example.inspect
55
55
  assert_equal expected, actual
56
56
  end
@@ -58,14 +58,14 @@ class TestAltStruct < MiniTest::Unit::TestCase
58
58
  def test_inspect_with_twice_inspected_struct
59
59
  @example.inspect
60
60
  @example.inspect
61
- expected = '#<AltStruct name="Kurtis", age=24>'
61
+ expected = "#<AltStruct name=\"Kurtis\", age=24>"
62
62
  actual = @example.inspect
63
63
  assert_equal expected, actual
64
64
  end
65
65
 
66
66
  def test_inspect_with_empty_sub_struct
67
67
  @empty.struct2 = AltStruct.new
68
- expected = '#<AltStruct struct2=#<AltStruct>>'
68
+ expected = "#<AltStruct struct2=#<AltStruct>>"
69
69
  actual = @empty.inspect
70
70
  assert_equal expected, actual
71
71
  end
@@ -91,7 +91,7 @@ class TestAltStruct < MiniTest::Unit::TestCase
91
91
  # @example.freeze
92
92
  # def @example.frozen?; nil end
93
93
  # @example.freeze
94
- # message = '[ruby-core:22559]'
94
+ # message = "[ruby-core:22559]"
95
95
  # assert_raises(RuntimeError, message) { @example.name = "Jazzy" }
96
96
  # # assert_raises(TypeError, message) { @example.name = "Jazzy" }
97
97
  # end
@@ -105,13 +105,13 @@ class TestAltStruct < MiniTest::Unit::TestCase
105
105
  end
106
106
 
107
107
  def test_delete_field_removes_getter_method
108
- bug = '[ruby-core:33010]'
108
+ bug = "[ruby-core:33010]"
109
109
  @example.delete_field :name
110
110
  refute_respond_to @example, :name, bug
111
111
  end
112
112
 
113
113
  def test_delete_field_removes_setter_method
114
- bug = '[ruby-core:33010]'
114
+ bug = "[ruby-core:33010]"
115
115
  @example.delete_field :name
116
116
  refute_respond_to @example, :name=, bug
117
117
  end
@@ -130,7 +130,7 @@ class TestAltStruct < MiniTest::Unit::TestCase
130
130
  end
131
131
 
132
132
  def test_method_missing_handles_square_bracket_equals
133
- assert_raises(ArgumentError) { @empty[:foo] = :bar }
133
+ assert_raises(NoMethodError) { @empty[:foo] = :bar }
134
134
  end
135
135
 
136
136
  def test_method_missing_handles_square_brackets