structure 0.22.1 → 0.23.0
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.
- data/Gemfile +4 -4
- data/LICENSE +18 -9
- data/README.md +5 -11
- data/lib/structure.rb +224 -146
- data/lib/structure/ext/active_support.rb +22 -0
- data/lib/structure/version.rb +1 -1
- data/structure.gemspec +2 -2
- data/test/defined_structure_test.rb +60 -0
- data/test/helper.rb +8 -0
- data/test/structure_test.rb +130 -78
- metadata +10 -6
- data/lib/structure/json.rb +0 -79
data/Gemfile
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
source :rubygems
|
2
2
|
gemspec
|
3
3
|
|
4
|
-
gem 'activesupport'
|
4
|
+
gem 'activesupport' # Required for testing Rails compatibility
|
5
|
+
gem 'pry' unless ENV['CI']
|
6
|
+
gem 'rake'
|
7
|
+
|
5
8
|
if RUBY_VERSION.include? '1.8'
|
6
9
|
gem 'json'
|
7
10
|
gem 'minitest'
|
8
11
|
end
|
9
|
-
|
10
|
-
gem 'pry' unless ENV['CI']
|
11
|
-
gem 'rake'
|
data/LICENSE
CHANGED
@@ -1,13 +1,22 @@
|
|
1
|
-
|
2
|
-
Version 2, December 2004
|
1
|
+
(The MIT License)
|
3
2
|
|
4
|
-
|
3
|
+
Copyright (c) 2011 Hakan Ensari
|
5
4
|
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
9
12
|
|
10
|
-
|
11
|
-
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
12
15
|
|
13
|
-
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
CHANGED
@@ -1,14 +1,8 @@
|
|
1
|
-
|
1
|
+

|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
Structure is a typed key/value container.
|
3
|
+
> Machines only work when they break down and by continually breaking
|
4
|
+
> down.
|
6
5
|
|
7
|
-
|
8
|
-
key :name
|
9
|
-
key :friends, Array, []
|
10
|
-
end
|
6
|
+
[Delve into the API.](http://rubydoc.info/github/hakanensari/structure/master/frames)
|
11
7
|
|
12
|
-
|
13
|
-
|
14
|
-
[1]: https://github.com/hakanensari/structure/wiki/
|
8
|
+
[](http://travis-ci.org/hakanensari/structure)
|
data/lib/structure.rb
CHANGED
@@ -1,189 +1,267 @@
|
|
1
|
-
|
1
|
+
begin
|
2
|
+
JSON::JSON_LOADED
|
3
|
+
rescue NameError
|
4
|
+
require 'json'
|
5
|
+
end
|
6
|
+
|
7
|
+
# Structure is a key/value container. On the most basic level, it
|
8
|
+
# mirrors the functionality of OpenStruct:
|
9
|
+
#
|
10
|
+
# require 'structure'
|
11
|
+
#
|
12
|
+
# record = Structure.new
|
13
|
+
# record.name = "John Smith"
|
14
|
+
# record.age = 70
|
15
|
+
# record.pension = 300
|
16
|
+
#
|
17
|
+
# puts record.name # -> "John Smith"
|
18
|
+
# puts record.address # -> nil
|
19
|
+
#
|
20
|
+
# Build structures recursively:
|
21
|
+
#
|
22
|
+
# hash = {
|
23
|
+
# "name" => "Australia",
|
24
|
+
# "population" => "20000000",
|
25
|
+
# "cities" => [
|
26
|
+
# {
|
27
|
+
# "name" => "Sydney",
|
28
|
+
# "population" => "4100000"
|
29
|
+
# },
|
30
|
+
# {
|
31
|
+
# "name" => "Melbourne",
|
32
|
+
# "population" => "4000000"
|
33
|
+
# } ]
|
34
|
+
# }
|
35
|
+
#
|
36
|
+
# country = Structure.new(hash)
|
37
|
+
# puts country.name # -> "Australia"
|
38
|
+
# puts country.cities.count # -> 2
|
39
|
+
# puts country.cities.first.name # -> "Sydney"
|
2
40
|
#
|
3
|
-
#
|
4
|
-
# class Person < Structure
|
5
|
-
# key :name
|
6
|
-
# key :friends, Array, []
|
7
|
-
# end
|
41
|
+
# Define optionally-typed fields in a structure:
|
8
42
|
#
|
43
|
+
# class Price < Structure
|
44
|
+
# field :cents, Integer
|
45
|
+
# field :currency, :default => "USD"
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# hash = { "cents" => "100" }
|
49
|
+
#
|
50
|
+
# price = Price.new(hash)
|
51
|
+
# puts price.cents # -> 100
|
52
|
+
# puts price.currency # -> "USD"
|
53
|
+
#
|
54
|
+
# Alternatively, define a proc to cast or otherwise manipulate assigned
|
55
|
+
# values:
|
56
|
+
#
|
57
|
+
# class Product < Structure
|
58
|
+
# field :sku, lambda(&:upcase)
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# product = Product.new(:sku => 'foo-bar')
|
62
|
+
# puts product.sku # -> "FOO-BAR"
|
63
|
+
#
|
64
|
+
# Structures are fully conversant in JSON, which is quite handy in the
|
65
|
+
# ephemeral landscape of APIs.
|
9
66
|
class Structure
|
10
|
-
|
11
|
-
|
12
|
-
BasicObject = BlankSlate
|
13
|
-
else
|
14
|
-
class BasicObject
|
15
|
-
instance_methods.each do |mth|
|
16
|
-
undef_method(mth) unless mth =~ /__/
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
67
|
+
class << self
|
68
|
+
attr_accessor :blueprint
|
21
69
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
# @param [Class] klass a class, which may already be wrapped
|
29
|
-
# @return [Wrapper] the wrapped class
|
30
|
-
def self.wrap(klass)
|
31
|
-
klass.class == self ? klass : new(klass.to_s)
|
70
|
+
# Builds a structure out of a JSON representation.
|
71
|
+
# @param [Hash] hsh a JSON representation translated to a hash
|
72
|
+
# @return [Structure] a structure
|
73
|
+
def json_create(hsh)
|
74
|
+
hsh.delete('json_class')
|
75
|
+
new(hsh)
|
32
76
|
end
|
33
77
|
|
34
|
-
# Creates a
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
78
|
+
# Creates a field.
|
79
|
+
# @overload field(key, opts = {})
|
80
|
+
# Creates a field.
|
81
|
+
# @param [#to_sym] key the name of the field
|
82
|
+
# @param [Hash] opts the options to create the field with
|
83
|
+
# @option opts [Object] :default the default value
|
84
|
+
# @overload field(key, type, opts = {})
|
85
|
+
# Creates a typed field.
|
86
|
+
# @param [#to_sym] key the name of the field
|
87
|
+
# @param [Class, Proc] type the type to cast assigned values
|
88
|
+
# @param [Hash] opts the options to create the field with
|
89
|
+
# @option opts [Object] :default the default value
|
90
|
+
def field(key, *args)
|
91
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
92
|
+
default = opts[:default]
|
93
|
+
type = args.shift
|
94
|
+
@blueprint[key] = { :type => type,
|
95
|
+
:default => default }
|
39
96
|
end
|
40
97
|
|
41
|
-
#
|
42
|
-
|
43
|
-
|
98
|
+
# Syntactic sugar to create a typed field that defaults to an empty
|
99
|
+
# array.
|
100
|
+
# @param key the name of the field
|
101
|
+
def many(key)
|
102
|
+
field(key, Array, :default => [])
|
44
103
|
end
|
45
104
|
|
46
|
-
|
47
|
-
#
|
48
|
-
# @return [Class] the unwrapped class
|
49
|
-
def unwrap
|
50
|
-
@name.split('::').inject(::Kernel) do |parent, child|
|
51
|
-
parent.const_get(child)
|
52
|
-
end
|
53
|
-
end
|
105
|
+
private
|
54
106
|
|
55
|
-
def
|
56
|
-
|
57
|
-
::Kernel.const_get(@name).send(mth, *args, &block)
|
58
|
-
ensure
|
59
|
-
@unwrapped = false
|
107
|
+
def inherited(child)
|
108
|
+
child.blueprint = blueprint.dup
|
60
109
|
end
|
61
110
|
end
|
62
111
|
|
63
|
-
|
64
|
-
class Definition
|
65
|
-
# Creates a key definition
|
66
|
-
#
|
67
|
-
# @param [Class] type the key type
|
68
|
-
# @param [Object] default an optional default value
|
69
|
-
def initialize(type, default = nil)
|
70
|
-
@wrapper = Wrapper.wrap(type)
|
71
|
-
@default = typecast(default)
|
72
|
-
end
|
73
|
-
|
74
|
-
# @return the default value for the key
|
75
|
-
attr :default
|
112
|
+
@blueprint = {}
|
76
113
|
|
77
|
-
|
78
|
-
|
79
|
-
|
114
|
+
# Creates a new structure.
|
115
|
+
# @param [Hash] hsh an optional hash to populate fields
|
116
|
+
def initialize(hsh = {})
|
117
|
+
# It may have improved performance if I had defined these methods
|
118
|
+
# on the class level, but I decided to privilege consistency here.
|
119
|
+
# Who wouldn't?
|
120
|
+
@table = blueprint.inject({}) do |a, (k, v)|
|
121
|
+
a.merge new_field(k, v[:type]) => v[:default]
|
80
122
|
end
|
81
123
|
|
82
|
-
|
83
|
-
#
|
84
|
-
# @param [Object] val a value
|
85
|
-
# @raise [TypeError] value isn't a type
|
86
|
-
# @return [Object] a typecast value
|
87
|
-
def typecast(val)
|
88
|
-
if val.nil?
|
89
|
-
nil
|
90
|
-
elsif val.is_a?(type)
|
91
|
-
val.dup rescue val
|
92
|
-
elsif Kernel.respond_to?(type.to_s)
|
93
|
-
Kernel.send(type.to_s, val)
|
94
|
-
else
|
95
|
-
raise TypeError, "#{val} isn't a #{type}"
|
96
|
-
end
|
97
|
-
end
|
124
|
+
marshal_load(hsh)
|
98
125
|
end
|
99
126
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
127
|
+
# Deletes a field.
|
128
|
+
# @param [#to_sym] key
|
129
|
+
# @return [Object] the value of the deleted field
|
130
|
+
def delete_field(key)
|
131
|
+
key = key.to_sym
|
132
|
+
class << self; self; end.class_eval do
|
133
|
+
[key, "#{key}="].each { |m| remove_method m }
|
104
134
|
end
|
105
135
|
|
106
|
-
|
107
|
-
|
108
|
-
# @note Key type defaults to +String+ if not specified.
|
109
|
-
#
|
110
|
-
# @param [#to_sym] name the key name
|
111
|
-
# @param [Class] type an optional key type
|
112
|
-
# @param [Object] default an optional default value
|
113
|
-
# @raise [NameError] name is already taken
|
114
|
-
def key(name, type = String, default = nil)
|
115
|
-
if method_defined?(name = name.to_sym)
|
116
|
-
raise NameError, "#{name} is taken"
|
117
|
-
end
|
136
|
+
@table.delete key
|
137
|
+
end
|
118
138
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
139
|
+
# Provides marshalling support for use by the Marshal library.
|
140
|
+
# @return [Hash] a hash of the keys and values of the structure
|
141
|
+
def marshal_dump
|
142
|
+
@table.inject({}) do |a, (k, v)|
|
143
|
+
a.merge k => recursively_dump(v)
|
124
144
|
end
|
145
|
+
end
|
125
146
|
|
126
|
-
|
127
|
-
|
147
|
+
# Provides marshalling support for use by the Marshal library.
|
148
|
+
# @param [Hash] hsh a hash of keys and values to populate the
|
149
|
+
# structure
|
150
|
+
def marshal_load(hsh)
|
151
|
+
hsh.each do |k, v|
|
152
|
+
self.send("#{new_field(k)}=", v)
|
128
153
|
end
|
129
154
|
end
|
130
155
|
|
131
|
-
#
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
a
|
138
|
-
end
|
156
|
+
# @return [String] a JSON representation of the structure
|
157
|
+
def to_json(*args)
|
158
|
+
{ JSON.create_id => self.class.name }.
|
159
|
+
merge(marshal_dump).
|
160
|
+
to_json(*args)
|
161
|
+
end
|
139
162
|
|
140
|
-
|
163
|
+
# @return [Boolean] whether the object and +other+ are equal
|
164
|
+
def ==(other)
|
165
|
+
other.is_a?(Structure) && @table == other.table
|
141
166
|
end
|
142
167
|
|
143
|
-
|
144
|
-
|
145
|
-
|
168
|
+
protected
|
169
|
+
|
170
|
+
attr :table
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def blueprint
|
175
|
+
self.class.blueprint
|
176
|
+
end
|
177
|
+
|
178
|
+
def initialize_copy(orig)
|
179
|
+
super
|
180
|
+
@table = @table.dup
|
146
181
|
end
|
147
182
|
|
148
|
-
def
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
183
|
+
def method_missing(mth, *args)
|
184
|
+
name = mth.to_s
|
185
|
+
len = args.length
|
186
|
+
if name.chomp!('=') && mth != :[]=
|
187
|
+
modifiable[new_field(name)] = recursively_load(args.first)
|
188
|
+
elsif len == 0
|
189
|
+
@table[new_field(mth)]
|
153
190
|
else
|
154
|
-
|
191
|
+
super
|
155
192
|
end
|
156
193
|
end
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
# @param [Object] other another object
|
165
|
-
# @return [true, false]
|
166
|
-
def ==(other)
|
167
|
-
other.is_a?(self.class) && attributes == other.attributes
|
194
|
+
|
195
|
+
def modifiable
|
196
|
+
if frozen?
|
197
|
+
raise RuntimeError, "can't modify frozen #{self.class}", caller(3)
|
198
|
+
end
|
199
|
+
|
200
|
+
@table
|
168
201
|
end
|
169
202
|
|
170
|
-
def
|
171
|
-
|
203
|
+
def new_field(key, type = nil)
|
204
|
+
key = key.to_sym
|
205
|
+
unless self.respond_to?(key)
|
206
|
+
class << self; self; end.class_eval do
|
207
|
+
define_method(key) { @table[key] }
|
208
|
+
|
209
|
+
assignment =
|
210
|
+
case type
|
211
|
+
when nil
|
212
|
+
lambda { |v| modifiable[key] = recursively_load(v) }
|
213
|
+
when Proc
|
214
|
+
lambda { |v| modifiable[key] = type.call(v) }
|
215
|
+
when Class
|
216
|
+
mth = type.to_s.to_sym
|
217
|
+
if Kernel.respond_to?(mth)
|
218
|
+
lambda { |v|
|
219
|
+
modifiable[key] = v.nil? ? nil : Kernel.send(mth, v)
|
220
|
+
}
|
221
|
+
else
|
222
|
+
lambda { |v|
|
223
|
+
modifiable[key] =
|
224
|
+
if v.nil? || v.is_a?(type)
|
225
|
+
v
|
226
|
+
else
|
227
|
+
raise TypeError, "#{v} isn't a #{type}"
|
228
|
+
end
|
229
|
+
}
|
230
|
+
end
|
231
|
+
else
|
232
|
+
raise TypeError, "#{type} isn't a valid type"
|
233
|
+
end
|
234
|
+
|
235
|
+
define_method("#{key}=", assignment)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
key
|
172
240
|
end
|
173
|
-
private :blueprint
|
174
241
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
242
|
+
def recursively_dump(val)
|
243
|
+
if val.respond_to? :marshal_dump
|
244
|
+
val.marshal_dump
|
245
|
+
elsif val.is_a? Array
|
246
|
+
val.map { |v| recursively_dump(v) }
|
247
|
+
else
|
248
|
+
val
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def recursively_load(val)
|
253
|
+
case val
|
254
|
+
when Hash
|
255
|
+
self.class.new(val)
|
256
|
+
when Array
|
257
|
+
val.map { |v| recursively_load(v) }
|
258
|
+
else
|
259
|
+
val
|
184
260
|
end
|
261
|
+
end
|
185
262
|
|
186
|
-
|
263
|
+
if defined? ActiveSupport
|
264
|
+
require 'structure/ext/active_support'
|
265
|
+
include Ext::ActiveSupport
|
187
266
|
end
|
188
|
-
private :modifiable
|
189
267
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Structure
|
2
|
+
module Ext
|
3
|
+
module ActiveSupport
|
4
|
+
def as_json(options = nil)
|
5
|
+
subset = if options
|
6
|
+
if only = options[:only]
|
7
|
+
marshal_dump.slice(*Array.wrap(only))
|
8
|
+
elsif except = options[:except]
|
9
|
+
marshal_dump.except(*Array.wrap(except))
|
10
|
+
else
|
11
|
+
marshal_dump
|
12
|
+
end
|
13
|
+
else
|
14
|
+
marshal_dump
|
15
|
+
end
|
16
|
+
|
17
|
+
{ JSON.create_id => self.class.name }.
|
18
|
+
merge(subset)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/structure/version.rb
CHANGED
data/structure.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.authors = ['Hakan Ensari']
|
10
10
|
s.email = ['code@papercavalier.com']
|
11
11
|
s.homepage = 'http://github.com/hakanensari/structure'
|
12
|
-
s.summary = 'A
|
13
|
-
s.description = 'Structure is a
|
12
|
+
s.summary = 'A key/value container'
|
13
|
+
s.description = 'Structure is a key/value container.'
|
14
14
|
|
15
15
|
s.rubyforge_project = 'structure'
|
16
16
|
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require File.expand_path('../helper.rb', __FILE__)
|
2
|
+
|
3
|
+
class Product < Structure
|
4
|
+
field :title
|
5
|
+
field :sku, lambda(&:upcase)
|
6
|
+
field :cents, Integer
|
7
|
+
field :currency, String, :default => 'USD'
|
8
|
+
field :in_stock, :default => true
|
9
|
+
many :related
|
10
|
+
end
|
11
|
+
|
12
|
+
class Foo < Structure
|
13
|
+
field :bar, Hash
|
14
|
+
end
|
15
|
+
|
16
|
+
class TestDefinedStructure < MiniTest::Unit::TestCase
|
17
|
+
def setup
|
18
|
+
@product = Product.new(:title => 'Widget')
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_inheritance
|
22
|
+
assert_equal 'USD', Class.new(Product).new.currency
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_equal_value
|
26
|
+
assert @product == Class.new(Product).new(:title => 'Widget')
|
27
|
+
refute @product == Product.new(:title => 'Widget', :sku => '123')
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_casting
|
31
|
+
@product.title = 1
|
32
|
+
assert_kind_of Integer, @product.title
|
33
|
+
|
34
|
+
@product.sku = 'sku-123'
|
35
|
+
assert_equal 'SKU-123', @product.sku
|
36
|
+
|
37
|
+
@product.cents = '1'
|
38
|
+
assert_kind_of Integer, @product.cents
|
39
|
+
|
40
|
+
@product.related = '1'
|
41
|
+
assert_kind_of Array, @product.related
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_default_values
|
45
|
+
assert_equal nil, @product.cents
|
46
|
+
assert_equal 'USD', @product.currency
|
47
|
+
assert_equal true, @product.in_stock
|
48
|
+
assert_equal [], @product.related
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_hashes_with_recursion
|
52
|
+
foo = Foo.new('bar' => { 'baz' => 1 })
|
53
|
+
hsh = foo.marshal_dump
|
54
|
+
foo.marshal_load(hsh)
|
55
|
+
assert_equal({ 'baz' => 1 }, foo.bar)
|
56
|
+
|
57
|
+
json = foo.to_json
|
58
|
+
assert foo, JSON.parse(json)
|
59
|
+
end
|
60
|
+
end
|
data/test/helper.rb
ADDED
data/test/structure_test.rb
CHANGED
@@ -1,109 +1,161 @@
|
|
1
|
-
require '
|
1
|
+
require File.expand_path('../helper.rb', __FILE__)
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
require 'structure'
|
9
|
-
require 'structure/json'
|
3
|
+
# Most tests below are borrowed from RubySpec.
|
4
|
+
class TestStructure < MiniTest::Unit::TestCase
|
5
|
+
def setup
|
6
|
+
@person = Structure.new(:name => 'John')
|
7
|
+
end
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
def test_delete_field
|
10
|
+
@person.delete_field(:name)
|
11
|
+
assert_nil @person.send(:table)[:name]
|
12
|
+
refute_respond_to(@person, :name)
|
13
|
+
refute_respond_to(@person, :name=)
|
14
|
+
end
|
16
15
|
|
17
|
-
|
18
|
-
|
19
|
-
end
|
16
|
+
def test_element_reference
|
17
|
+
assert_raises(NoMethodError) { @person[1] }
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
23
|
-
wrapper = Structure::Wrapper.new(:Foo)
|
24
|
-
assert_raises(NameError) { wrapper.bar }
|
25
|
-
assert_raises(NameError) { wrapper.unwrap.bar }
|
26
|
-
|
27
|
-
klass = Class.new { def self.bar; end }
|
28
|
-
::Kernel.const_set(:Foo, klass)
|
29
|
-
assert_respond_to wrapper, :bar
|
30
|
-
assert_equal Foo, wrapper.unwrap
|
20
|
+
def test_element_set
|
21
|
+
assert_raises(NoMethodError) { @person[1] = 2 }
|
31
22
|
end
|
32
23
|
|
33
|
-
def
|
34
|
-
|
35
|
-
|
24
|
+
def test_equal_value
|
25
|
+
refute @person == 'foo'
|
26
|
+
assert @person == @person
|
27
|
+
assert @person == Structure.new(:name => 'John')
|
28
|
+
assert @person == Class.new(Structure).new(:name => 'John')
|
29
|
+
refute @person == Structure.new(:name => 'Johnny')
|
30
|
+
refute @person == Structure.new(:name => 'John', :age => 20)
|
36
31
|
end
|
37
32
|
|
38
|
-
def
|
39
|
-
|
40
|
-
|
33
|
+
def test_frozen
|
34
|
+
@person.freeze
|
35
|
+
|
36
|
+
assert_equal 'John', @person.name
|
37
|
+
assert_raises(RuntimeError) { @person.age = 42 }
|
38
|
+
assert_raises(RuntimeError) { @person.state = :new }
|
39
|
+
|
40
|
+
c = @person.clone
|
41
|
+
assert_equal 'John', c.name
|
42
|
+
assert_raises(RuntimeError) { c.age = 42 }
|
43
|
+
assert_raises(RuntimeError) { c.state = :new }
|
44
|
+
|
45
|
+
d = @person.dup
|
46
|
+
assert_equal 'John', d.name
|
47
|
+
d.age = 42
|
48
|
+
assert_equal 42, d.age
|
41
49
|
end
|
42
50
|
|
43
|
-
def
|
44
|
-
|
51
|
+
def test_initialize_copy
|
52
|
+
d = @person.dup
|
53
|
+
d.name = 'Jane'
|
54
|
+
assert_equal 'Jane', d.name
|
55
|
+
assert_equal 'John', @person.name
|
56
|
+
|
57
|
+
@person.friends = ['Joe']
|
58
|
+
d = @person.dup
|
59
|
+
d.friends = ['Jim']
|
60
|
+
assert_equal ['Jim'], d.friends
|
61
|
+
assert_equal ['Joe'], @person.friends
|
45
62
|
end
|
46
63
|
|
47
|
-
def
|
48
|
-
|
49
|
-
person.
|
50
|
-
|
64
|
+
def test_json
|
65
|
+
friend = Structure.new(:name => 'Jane')
|
66
|
+
@person.friend = friend
|
67
|
+
@person.cities = ['Zurich']
|
68
|
+
json = '{"json_class":"Structure",
|
69
|
+
"name":"John",
|
70
|
+
"friend":{"name":"Jane"},
|
71
|
+
"cities":["Zurich"]}'.gsub(/\s+/, '')
|
72
|
+
assert_equal @person, JSON.parse(json)
|
73
|
+
assert_equal friend, JSON.parse(json).friend
|
74
|
+
assert_equal 'Zurich', JSON.parse(json).cities.first
|
75
|
+
|
76
|
+
refute_respond_to @person, :as_json
|
77
|
+
require 'active_support/ordered_hash'
|
78
|
+
require 'active_support/json'
|
79
|
+
load 'structure.rb'
|
80
|
+
assert @person.as_json(:only => :name).has_key?(:name)
|
81
|
+
refute @person.as_json(:except => :name).has_key?(:name)
|
82
|
+
end
|
51
83
|
|
52
|
-
|
53
|
-
|
84
|
+
def test_marshaling
|
85
|
+
assert_equal({ :name => 'John' }, @person.marshal_dump)
|
86
|
+
@person.marshal_load(:age => 20, :name => 'Jane')
|
87
|
+
assert_equal 20, @person.age
|
88
|
+
assert_equal 'Jane', @person.name
|
54
89
|
end
|
55
90
|
|
56
|
-
def
|
57
|
-
person =
|
58
|
-
|
91
|
+
def test_method_missing
|
92
|
+
@person.test = 'test'
|
93
|
+
assert_respond_to @person, :test
|
94
|
+
assert_respond_to @person, :test=
|
95
|
+
assert_equal 'test', @person.test
|
96
|
+
assert_equal 'test', @person.send(:table)[:test]
|
97
|
+
@person.test = 'changed'
|
98
|
+
assert_equal 'changed', @person.test
|
99
|
+
|
59
100
|
|
60
|
-
person.
|
61
|
-
assert_equal
|
62
|
-
|
101
|
+
@person.send(:table)[:age] = 20
|
102
|
+
assert_equal 20, @person.age
|
103
|
+
|
104
|
+
assert_raises(NoMethodError) { @person.gender(1) }
|
105
|
+
assert_nil @person.gender
|
106
|
+
|
107
|
+
@person.freeze
|
108
|
+
assert_raises(RuntimeError) { @person.gender = 'male' }
|
63
109
|
end
|
64
110
|
|
65
111
|
def test_new
|
66
|
-
person =
|
112
|
+
person = Structure.new(:name => 'John', :age => 70)
|
67
113
|
assert_equal 'John', person.name
|
68
|
-
|
69
|
-
|
70
|
-
assert_equal 'John', other.friends.first.name
|
114
|
+
assert_equal 70, person.age
|
115
|
+
assert_equal({}, Structure.new.send(:table))
|
71
116
|
end
|
72
117
|
|
73
|
-
def
|
74
|
-
person
|
75
|
-
person.
|
76
|
-
assert_raises(TypeError) { person.name = 'Jane' }
|
77
|
-
end
|
118
|
+
def test_new_field
|
119
|
+
@person.send(:table)[:age] = 20
|
120
|
+
@person.send(:new_field, :age)
|
78
121
|
|
79
|
-
|
80
|
-
person = Person.new(:name => 'John')
|
81
|
-
friend = Person.new(:name => 'Jane')
|
82
|
-
person.friends << friend
|
83
|
-
hsh = person.to_hash
|
122
|
+
assert_equal 20, @person.age
|
84
123
|
|
85
|
-
|
86
|
-
assert_equal
|
124
|
+
@person.age = 30
|
125
|
+
assert_equal 30, @person.age
|
87
126
|
|
88
|
-
person.
|
89
|
-
|
90
|
-
assert_equal
|
127
|
+
@person.instance_eval { def gender; 'male'; end }
|
128
|
+
@person.send(:new_field, :gender)
|
129
|
+
assert_equal 'male', @person.gender
|
130
|
+
refute_respond_to @person, :gender=
|
91
131
|
end
|
92
132
|
|
93
|
-
def
|
94
|
-
|
133
|
+
def test_recursive_assignment
|
134
|
+
friend = { :name => 'Jane' }
|
135
|
+
@person.friend = friend
|
136
|
+
@person.friends = [friend]
|
137
|
+
assert_equal 'Jane', @person.friend.name
|
138
|
+
assert_equal 'Jane', @person.friends.first.name
|
139
|
+
end
|
95
140
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
141
|
+
def test_recursive_marshaling
|
142
|
+
hsh = {
|
143
|
+
:name => 'John',
|
144
|
+
:friend => { :name => 'Jane' },
|
145
|
+
:friends => [{ :name => 'Jane' }]
|
146
|
+
}
|
147
|
+
friend = Structure.new(:name => 'Jane')
|
148
|
+
@person.friend = friend
|
149
|
+
@person.friends = [friend]
|
150
|
+
assert_equal hsh, @person.marshal_dump
|
151
|
+
|
152
|
+
person = Structure.new
|
153
|
+
person.marshal_load(hsh)
|
154
|
+
assert_equal friend, person.friend
|
155
|
+
assert_equal friend, person.friends.first
|
156
|
+
end
|
102
157
|
|
103
|
-
|
104
|
-
|
105
|
-
load 'structure/json.rb'
|
106
|
-
assert_equal true, person.as_json(:only => :name).has_key?(:name)
|
107
|
-
assert_equal false, person.as_json(:except => :name).has_key?(:name)
|
158
|
+
def test_table
|
159
|
+
assert_equal({ :name => 'John' }, @person.send(:table))
|
108
160
|
end
|
109
161
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: structure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.23.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,9 +9,9 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2011-
|
12
|
+
date: 2011-12-07 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
|
-
description: Structure is a
|
14
|
+
description: Structure is a key/value container.
|
15
15
|
email:
|
16
16
|
- code@papercavalier.com
|
17
17
|
executables: []
|
@@ -25,9 +25,11 @@ files:
|
|
25
25
|
- README.md
|
26
26
|
- Rakefile
|
27
27
|
- lib/structure.rb
|
28
|
-
- lib/structure/
|
28
|
+
- lib/structure/ext/active_support.rb
|
29
29
|
- lib/structure/version.rb
|
30
30
|
- structure.gemspec
|
31
|
+
- test/defined_structure_test.rb
|
32
|
+
- test/helper.rb
|
31
33
|
- test/structure_test.rb
|
32
34
|
homepage: http://github.com/hakanensari/structure
|
33
35
|
licenses: []
|
@@ -49,9 +51,11 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
49
51
|
version: '0'
|
50
52
|
requirements: []
|
51
53
|
rubyforge_project: structure
|
52
|
-
rubygems_version: 1.8.
|
54
|
+
rubygems_version: 1.8.11
|
53
55
|
signing_key:
|
54
56
|
specification_version: 3
|
55
|
-
summary: A
|
57
|
+
summary: A key/value container
|
56
58
|
test_files:
|
59
|
+
- test/defined_structure_test.rb
|
60
|
+
- test/helper.rb
|
57
61
|
- test/structure_test.rb
|
data/lib/structure/json.rb
DELETED
@@ -1,79 +0,0 @@
|
|
1
|
-
begin
|
2
|
-
JSON::JSON_LOADED
|
3
|
-
rescue NameError
|
4
|
-
require 'json'
|
5
|
-
end
|
6
|
-
|
7
|
-
class Structure
|
8
|
-
# JSON methods for a structure
|
9
|
-
#
|
10
|
-
# Include this in your structure if you need to cast it to JSON and
|
11
|
-
# vice versa.
|
12
|
-
#
|
13
|
-
# @example
|
14
|
-
# class Point < Structure
|
15
|
-
# include JSON
|
16
|
-
#
|
17
|
-
# key :x, Integer
|
18
|
-
# key :y, Integer
|
19
|
-
# end
|
20
|
-
#
|
21
|
-
# Alternatively, include it in the parent class if you have more than
|
22
|
-
# one structure.
|
23
|
-
#
|
24
|
-
# @example
|
25
|
-
# class Structure
|
26
|
-
# include JSON
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
module JSON
|
30
|
-
def self.included(base)
|
31
|
-
base.extend ClassMethods
|
32
|
-
end
|
33
|
-
|
34
|
-
# Converts structure to its JSON representation
|
35
|
-
#
|
36
|
-
# @param [Hash] args
|
37
|
-
# @return [JSON] a JSON representation of the structure
|
38
|
-
def to_json(*args)
|
39
|
-
{ ::JSON.create_id => self.class.name }.
|
40
|
-
merge(@attributes).
|
41
|
-
to_json(*args)
|
42
|
-
end
|
43
|
-
|
44
|
-
if defined? ActiveSupport
|
45
|
-
# Converts structure to its JSON representation
|
46
|
-
#
|
47
|
-
# @param [Hash] options
|
48
|
-
# @return [JSON] a JSON representation of the structure
|
49
|
-
def as_json(options = nil)
|
50
|
-
subset = if options
|
51
|
-
if only = options[:only]
|
52
|
-
@attributes.slice(*Array.wrap(only))
|
53
|
-
elsif except = options[:except]
|
54
|
-
@attributes.except(*Array.wrap(except))
|
55
|
-
else
|
56
|
-
@attributes.dup
|
57
|
-
end
|
58
|
-
else
|
59
|
-
@attributes.dup
|
60
|
-
end
|
61
|
-
|
62
|
-
{ ::JSON.create_id => self.class.name }.
|
63
|
-
merge(subset)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
module ClassMethods
|
68
|
-
# Builds a structure out of its JSON representation
|
69
|
-
#
|
70
|
-
# @param [Hash] hsh a hash representation of a JSON
|
71
|
-
# @return [Structure] a structure
|
72
|
-
def json_create(hsh)
|
73
|
-
hsh.delete('json_class')
|
74
|
-
|
75
|
-
new(hsh)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|