structure 0.24.0 → 0.25.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.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -10
- data/LICENSE +1 -1
- data/README.md +27 -5
- data/Rakefile +5 -5
- data/lib/structure.rb +25 -261
- data/lib/structure/version.rb +2 -2
- data/structure.gemspec +18 -16
- data/test/structure_test.rb +30 -140
- metadata +42 -23
- data/.travis.yml +0 -8
- data/.yardopts +0 -2
- data/lib/structure/ext/active_support.rb +0 -23
- data/test/helper.rb +0 -8
- data/test/structure_with_fields_test.rb +0 -80
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d097be9799d796318524e7594f98050b1d41be8
|
4
|
+
data.tar.gz: 710fa928be479806b678fc9c17ae3222d71f059b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1c5f7f760400a8eb044df7efaa9a2d08ed8b0128db23659f64bbf4a98b9d2311f4772176a93c52c90745a0da4658aeba9cd85eaaf8719dfce2e3caa5b07973bf
|
7
|
+
data.tar.gz: 1d1a43d8aac01183286e82832f38540b03f5a5d7c2f2487fefc5cf2ad422b03e433de0c7f0394663a06b0948b91dbafa2b0993a7f77e3cbe25ed9c9a92d0dd4b
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -1,8 +1,30 @@
|
|
1
|
-
|
1
|
+
# Structure
|
2
2
|
|
3
|
-
|
4
|
-
> down.
|
3
|
+
Turn data (e.g. API responses) into immutable value objects in Ruby.
|
5
4
|
|
6
|
-
|
5
|
+
## Usage
|
7
6
|
|
8
|
-
|
7
|
+
Mix in Structure and define values with `.value`.
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
class Location
|
11
|
+
include Structure
|
12
|
+
|
13
|
+
attr :res
|
14
|
+
|
15
|
+
def initialize(res)
|
16
|
+
@res = res
|
17
|
+
end
|
18
|
+
|
19
|
+
value :latitude do
|
20
|
+
res.fetch(:lat)
|
21
|
+
end
|
22
|
+
|
23
|
+
value :longitude do
|
24
|
+
res.fetch(:lng)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
location = Location.new(lat: 10, lng: 100)
|
29
|
+
location.to_h # {:latitude=>10, :longitude=>100}
|
30
|
+
```
|
data/Rakefile
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
require 'bundler/gem_tasks'
|
2
2
|
require 'rake/testtask'
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
test.libs += %w{lib test}
|
8
|
-
test.test_files = FileList['test/**/*_test.rb']
|
4
|
+
Rake::TestTask.new do |t|
|
5
|
+
t.libs.push('lib')
|
6
|
+
t.test_files = FileList['test/**/*_test.rb']
|
9
7
|
end
|
8
|
+
|
9
|
+
task :default => [:test]
|
data/lib/structure.rb
CHANGED
@@ -1,278 +1,42 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
end
|
6
|
-
|
7
|
-
# Structure is a key/value container. On the most basic level, it mirrors the
|
8
|
-
# 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"
|
40
|
-
#
|
41
|
-
# Define optionally-typed fields in a structure:
|
42
|
-
#
|
43
|
-
# class Price < Structure
|
44
|
-
# field :cents, Integer
|
45
|
-
# field :currency, String, :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 values and
|
55
|
-
# assign defaults:
|
56
|
-
#
|
57
|
-
# class Product < Structure
|
58
|
-
# field :sku, lambda(&:upcase)
|
59
|
-
# field :created_at, String, :default => lambda { Time.now.to_s }
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# product = Product.new(:sku => 'foo-bar')
|
63
|
-
# puts product.sku # -> "FOO-BAR"
|
64
|
-
#
|
65
|
-
# Structures are fully conversant in JSON, which is quite handy in the
|
66
|
-
# ephemeral landscape of APIs.
|
67
|
-
class Structure
|
68
|
-
class << self
|
69
|
-
# @private
|
70
|
-
attr_accessor :blueprint
|
71
|
-
|
72
|
-
# Builds a structure out of a JSON representation.
|
73
|
-
# @param [Hash] hsh a JSON representation translated to a hash
|
74
|
-
# @return [Structure] a structure
|
75
|
-
def json_create(hsh)
|
76
|
-
hsh.delete('json_class')
|
77
|
-
new(hsh)
|
78
|
-
end
|
79
|
-
|
80
|
-
# Creates a field.
|
81
|
-
# @overload field(key, opts = {})
|
82
|
-
# Creates a field.
|
83
|
-
# @param [#to_sym] key the name of the field
|
84
|
-
# @param [Hash] opts the options to create the field with
|
85
|
-
# @option opts [Object] :default the default value
|
86
|
-
# @overload field(key, type, opts = {})
|
87
|
-
# Creates a typed field.
|
88
|
-
# @param [#to_sym] key the name of the field
|
89
|
-
# @param [Class, Proc] type the type to cast assigned values
|
90
|
-
# @param [Hash] opts the options to create the field with
|
91
|
-
# @option opts [Object] :default the default value
|
92
|
-
def field(key, *args)
|
93
|
-
opts = args.last.is_a?(Hash) ? args.pop : {}
|
94
|
-
default = opts[:default]
|
95
|
-
type = args.shift
|
96
|
-
@blueprint[key] = { :type => type,
|
97
|
-
:default => default }
|
98
|
-
end
|
99
|
-
|
100
|
-
alias key field
|
101
|
-
|
102
|
-
# Syntactic sugar to create a typed field that defaults to an empty array.
|
103
|
-
# @param key the name of the field
|
104
|
-
def many(key)
|
105
|
-
field(key, Array, :default => [])
|
106
|
-
end
|
107
|
-
|
108
|
-
# Syntactic sugar to create a field that stands in for another structure.
|
109
|
-
# @param key the name of the field
|
110
|
-
def one(key)
|
111
|
-
field(key, lambda { |v| v.is_a?(Structure) ? v : Structure.new(v) })
|
112
|
-
end
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
def inherited(child)
|
117
|
-
child.blueprint = blueprint.dup
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
@blueprint = {}
|
122
|
-
|
123
|
-
# Creates a new structure.
|
124
|
-
# @param [Hash] hsh an optional hash to populate fields
|
125
|
-
def initialize(hsh = {})
|
126
|
-
@table = blueprint.inject({}) do |a, (k, v)|
|
127
|
-
default = if v[:default].is_a? Proc
|
128
|
-
v[:default].call
|
129
|
-
else
|
130
|
-
v[:default].dup rescue v[:default]
|
131
|
-
end
|
132
|
-
|
133
|
-
a.merge new_field(k, v[:type]) => default
|
134
|
-
end
|
135
|
-
|
136
|
-
marshal_load(hsh)
|
1
|
+
module Structure
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
base.instance_variable_set(:@value_names, [])
|
137
5
|
end
|
138
6
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
def delete_field(key)
|
143
|
-
key = key.to_sym
|
144
|
-
class << self; self; end.class_eval do
|
145
|
-
[key, "#{key}="].each { |m| remove_method m }
|
146
|
-
end
|
147
|
-
|
148
|
-
@table.delete key
|
149
|
-
end
|
7
|
+
def values
|
8
|
+
vals = {}
|
9
|
+
self.class.value_names.each { |name| vals[name] = self.send(name) }
|
150
10
|
|
151
|
-
|
152
|
-
# @return [Hash] a hash of the keys and values of the structure
|
153
|
-
def marshal_dump
|
154
|
-
@table.inject({}) do |a, (k, v)|
|
155
|
-
a.merge k => recursively_dump(v)
|
156
|
-
end
|
11
|
+
vals
|
157
12
|
end
|
13
|
+
alias :to_h :values
|
158
14
|
|
159
|
-
# Provides marshalling support for use by the Marshal library.
|
160
|
-
# @param [Hash] hsh a hash of keys and values to populate the structure
|
161
|
-
def marshal_load(hsh)
|
162
|
-
hsh.each do |k, v|
|
163
|
-
self.send("#{new_field(k)}=", v)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# @return [String] a JSON representation of the structure
|
168
|
-
def to_json(*args)
|
169
|
-
{ JSON.create_id => self.class.name }.
|
170
|
-
merge(marshal_dump).
|
171
|
-
to_json(*args)
|
172
|
-
end
|
173
|
-
|
174
|
-
# @return [Boolean] whether the object and +other+ are equal
|
175
15
|
def ==(other)
|
176
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
protected
|
180
|
-
|
181
|
-
attr :table
|
182
|
-
|
183
|
-
private
|
184
|
-
|
185
|
-
def blueprint
|
186
|
-
self.class.blueprint
|
16
|
+
values == other.values
|
187
17
|
end
|
18
|
+
alias :eql? :==
|
188
19
|
|
189
|
-
def
|
190
|
-
|
191
|
-
@table = @table.dup
|
192
|
-
end
|
20
|
+
def inspect
|
21
|
+
str = "#<#{self.class}"
|
193
22
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
elsif len == 0
|
200
|
-
@table[new_field(mth)]
|
201
|
-
else
|
202
|
-
super
|
23
|
+
first = true
|
24
|
+
values.each do |k, v|
|
25
|
+
str << ',' unless first
|
26
|
+
first = false
|
27
|
+
str << " #{k}=#{v.inspect}"
|
203
28
|
end
|
204
|
-
end
|
205
29
|
|
206
|
-
|
207
|
-
if frozen?
|
208
|
-
raise RuntimeError, "can't modify frozen #{self.class}", caller(3)
|
209
|
-
end
|
210
|
-
|
211
|
-
@table
|
30
|
+
str << '>'
|
212
31
|
end
|
32
|
+
alias :to_s :inspect
|
213
33
|
|
214
|
-
|
215
|
-
|
216
|
-
unless self.respond_to?(key)
|
217
|
-
class << self; self; end.class_eval do
|
218
|
-
define_method(key) { @table[key] }
|
219
|
-
|
220
|
-
assignment =
|
221
|
-
case type
|
222
|
-
when nil
|
223
|
-
lambda { |v| modifiable[key] = recursively_load(v) }
|
224
|
-
when Proc
|
225
|
-
lambda { |v| modifiable[key] = type.call(v) }
|
226
|
-
when Class
|
227
|
-
mth = type.to_s.to_sym
|
228
|
-
if Kernel.respond_to?(mth)
|
229
|
-
lambda { |v|
|
230
|
-
modifiable[key] = v.nil? ? nil : Kernel.send(mth, v)
|
231
|
-
}
|
232
|
-
else
|
233
|
-
lambda { |v|
|
234
|
-
modifiable[key] =
|
235
|
-
if v.nil? || v.is_a?(type)
|
236
|
-
v
|
237
|
-
else
|
238
|
-
raise TypeError, "#{v} isn't a #{type}"
|
239
|
-
end
|
240
|
-
}
|
241
|
-
end
|
242
|
-
else
|
243
|
-
raise TypeError, "#{type} isn't a valid type"
|
244
|
-
end
|
34
|
+
module ClassMethods
|
35
|
+
attr :value_names
|
245
36
|
|
246
|
-
|
247
|
-
|
37
|
+
def value(name, &blk)
|
38
|
+
define_method(name, &blk)
|
39
|
+
@value_names << name
|
248
40
|
end
|
249
|
-
|
250
|
-
key
|
251
|
-
end
|
252
|
-
|
253
|
-
def recursively_dump(val)
|
254
|
-
if val.respond_to? :marshal_dump
|
255
|
-
val.marshal_dump
|
256
|
-
elsif val.is_a? Array
|
257
|
-
val.map { |v| recursively_dump(v) }
|
258
|
-
else
|
259
|
-
val
|
260
|
-
end
|
261
|
-
end
|
262
|
-
|
263
|
-
def recursively_load(val)
|
264
|
-
case val
|
265
|
-
when Hash
|
266
|
-
self.class.new(val)
|
267
|
-
when Array
|
268
|
-
val.map { |v| recursively_load(v) }
|
269
|
-
else
|
270
|
-
val
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
if defined? ActiveSupport
|
275
|
-
require 'structure/ext/active_support'
|
276
|
-
include Ext::ActiveSupport
|
277
41
|
end
|
278
42
|
end
|
data/lib/structure/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.
|
1
|
+
module Structure
|
2
|
+
VERSION = '0.25.0'
|
3
3
|
end
|
data/structure.gemspec
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
4
|
require 'structure/version'
|
4
5
|
|
5
|
-
Gem::Specification.new do |
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'structure'
|
8
|
+
spec.version = Structure::VERSION
|
9
|
+
spec.authors = ['Hakan Ensari']
|
10
|
+
spec.email = ['hakan.ensari@papercavalier.com']
|
11
|
+
spec.description = 'Value objects in Ruby'
|
12
|
+
spec.summary = 'Value objects in Ruby'
|
13
|
+
spec.homepage = 'http://github.com/hakanensari/structure'
|
14
|
+
spec.license = 'MIT'
|
14
15
|
|
15
|
-
|
16
|
+
spec.files = `git ls-files`.split("\n")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
16
20
|
|
17
|
-
|
18
|
-
|
19
|
-
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
-
s.require_paths = ['lib']
|
21
|
+
spec.add_development_dependency 'minitest'
|
22
|
+
spec.add_development_dependency 'rake'
|
21
23
|
end
|
data/test/structure_test.rb
CHANGED
@@ -1,161 +1,51 @@
|
|
1
|
-
require
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'structure'
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
def setup
|
6
|
-
@person = Structure.new(:name => 'John')
|
7
|
-
end
|
8
|
-
|
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
|
15
|
-
|
16
|
-
def test_element_reference
|
17
|
-
assert_raises(NoMethodError) { @person[1] }
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_element_set
|
21
|
-
assert_raises(NoMethodError) { @person[1] = 2 }
|
22
|
-
end
|
23
|
-
|
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)
|
31
|
-
end
|
32
|
-
|
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 }
|
4
|
+
class Location
|
5
|
+
include Structure
|
44
6
|
|
45
|
-
|
46
|
-
assert_equal 'John', d.name
|
47
|
-
d.age = 42
|
48
|
-
assert_equal 42, d.age
|
49
|
-
end
|
50
|
-
|
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
|
7
|
+
attr :res
|
56
8
|
|
57
|
-
|
58
|
-
|
59
|
-
d.friends = ['Jim']
|
60
|
-
assert_equal ['Jim'], d.friends
|
61
|
-
assert_equal ['Joe'], @person.friends
|
9
|
+
def initialize(res)
|
10
|
+
@res = res
|
62
11
|
end
|
63
12
|
|
64
|
-
|
65
|
-
|
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)
|
13
|
+
value :latitude do
|
14
|
+
res.fetch(:lat)
|
82
15
|
end
|
83
16
|
|
84
|
-
|
85
|
-
|
86
|
-
@person.marshal_load(:age => 20, :name => 'Jane')
|
87
|
-
assert_equal 20, @person.age
|
88
|
-
assert_equal 'Jane', @person.name
|
17
|
+
value :longitude do
|
18
|
+
res.fetch(:lng)
|
89
19
|
end
|
20
|
+
end
|
90
21
|
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
100
|
-
|
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' }
|
22
|
+
class StructureTest < MiniTest::Test
|
23
|
+
def setup
|
24
|
+
@location = Location.new(lat: 10, lng: 100)
|
109
25
|
end
|
110
26
|
|
111
|
-
def
|
112
|
-
|
113
|
-
assert_equal
|
114
|
-
assert_equal 70, person.age
|
115
|
-
assert_equal({}, Structure.new.send(:table))
|
27
|
+
def test_has_value
|
28
|
+
assert_equal 10, @location.latitude
|
29
|
+
assert_equal 100, @location.longitude
|
116
30
|
end
|
117
31
|
|
118
|
-
def
|
119
|
-
|
120
|
-
@person.send(:new_field, :age)
|
121
|
-
|
122
|
-
assert_equal 20, @person.age
|
123
|
-
|
124
|
-
@person.age = 30
|
125
|
-
assert_equal 30, @person.age
|
126
|
-
|
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=
|
32
|
+
def test_class_returns_value_names
|
33
|
+
assert_equal [:latitude, :longitude], Location.value_names
|
131
34
|
end
|
132
35
|
|
133
|
-
def
|
134
|
-
|
135
|
-
@
|
136
|
-
@person.friends = [friend]
|
137
|
-
assert_equal 'Jane', @person.friend.name
|
138
|
-
assert_equal 'Jane', @person.friends.first.name
|
36
|
+
def test_returns_values
|
37
|
+
assert_equal({ latitude: 10, longitude: 100 }, @location.values)
|
38
|
+
assert_equal @location.to_h, @location.values
|
139
39
|
end
|
140
40
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
|
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
|
41
|
+
def test_compares
|
42
|
+
@other = Location.new(lng: 100, lat: 10)
|
43
|
+
assert @location == @other
|
44
|
+
assert @location.eql?(@other)
|
156
45
|
end
|
157
46
|
|
158
|
-
def
|
159
|
-
assert_equal
|
47
|
+
def test_pretty_inspects
|
48
|
+
assert_equal '#<Location latitude=10, longitude=100>', @location.inspect
|
49
|
+
assert_equal @location.to_s, @location.inspect
|
160
50
|
end
|
161
51
|
end
|
metadata
CHANGED
@@ -1,63 +1,82 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: structure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.25.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Hakan Ensari
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
13
|
-
dependencies:
|
14
|
-
|
11
|
+
date: 2013-08-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Value objects in Ruby
|
15
42
|
email:
|
16
|
-
-
|
43
|
+
- hakan.ensari@papercavalier.com
|
17
44
|
executables: []
|
18
45
|
extensions: []
|
19
46
|
extra_rdoc_files: []
|
20
47
|
files:
|
21
48
|
- .gitignore
|
22
|
-
- .travis.yml
|
23
|
-
- .yardopts
|
24
49
|
- Gemfile
|
25
50
|
- LICENSE
|
26
51
|
- README.md
|
27
52
|
- Rakefile
|
28
53
|
- lib/structure.rb
|
29
|
-
- lib/structure/ext/active_support.rb
|
30
54
|
- lib/structure/version.rb
|
31
55
|
- structure.gemspec
|
32
|
-
- test/helper.rb
|
33
56
|
- test/structure_test.rb
|
34
|
-
- test/structure_with_fields_test.rb
|
35
57
|
homepage: http://github.com/hakanensari/structure
|
36
|
-
licenses:
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
metadata: {}
|
37
61
|
post_install_message:
|
38
62
|
rdoc_options: []
|
39
63
|
require_paths:
|
40
64
|
- lib
|
41
65
|
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
-
none: false
|
43
66
|
requirements:
|
44
|
-
- -
|
67
|
+
- - '>='
|
45
68
|
- !ruby/object:Gem::Version
|
46
69
|
version: '0'
|
47
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
-
none: false
|
49
71
|
requirements:
|
50
|
-
- -
|
72
|
+
- - '>='
|
51
73
|
- !ruby/object:Gem::Version
|
52
74
|
version: '0'
|
53
75
|
requirements: []
|
54
|
-
rubyforge_project:
|
55
|
-
rubygems_version:
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.0.5
|
56
78
|
signing_key:
|
57
|
-
specification_version:
|
58
|
-
summary:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Value objects in Ruby
|
59
81
|
test_files:
|
60
|
-
- test/helper.rb
|
61
82
|
- test/structure_test.rb
|
62
|
-
- test/structure_with_fields_test.rb
|
63
|
-
has_rdoc:
|
data/.travis.yml
DELETED
data/.yardopts
DELETED
@@ -1,23 +0,0 @@
|
|
1
|
-
class Structure
|
2
|
-
# @private
|
3
|
-
module Ext
|
4
|
-
module ActiveSupport
|
5
|
-
def as_json(options = nil)
|
6
|
-
subset = if options
|
7
|
-
if only = options[:only]
|
8
|
-
marshal_dump.slice(*Array.wrap(only))
|
9
|
-
elsif except = options[:except]
|
10
|
-
marshal_dump.except(*Array.wrap(except))
|
11
|
-
else
|
12
|
-
marshal_dump
|
13
|
-
end
|
14
|
-
else
|
15
|
-
marshal_dump
|
16
|
-
end
|
17
|
-
|
18
|
-
{ JSON.create_id => self.class.name }.
|
19
|
-
merge(subset)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
data/test/helper.rb
DELETED
@@ -1,80 +0,0 @@
|
|
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
|
-
key :in_stock, :default => true
|
9
|
-
field :created_on, :default => lambda { Date.today }
|
10
|
-
many :related
|
11
|
-
one :manufacturer
|
12
|
-
end
|
13
|
-
|
14
|
-
class Foo < Structure
|
15
|
-
field :bar, Hash
|
16
|
-
end
|
17
|
-
|
18
|
-
class TestStructureWithFields < MiniTest::Unit::TestCase
|
19
|
-
def setup
|
20
|
-
@product = Product.new(:title => 'Widget')
|
21
|
-
end
|
22
|
-
|
23
|
-
def test_inheritance
|
24
|
-
assert_equal 'USD', Class.new(Product).new.currency
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_equal_value
|
28
|
-
assert @product == Class.new(Product).new(:title => 'Widget')
|
29
|
-
refute @product == Product.new(:title => 'Widget', :sku => '123')
|
30
|
-
end
|
31
|
-
|
32
|
-
def test_casting
|
33
|
-
@product.title = 1
|
34
|
-
assert_kind_of Integer, @product.title
|
35
|
-
|
36
|
-
@product.sku = 'sku-123'
|
37
|
-
assert_equal 'SKU-123', @product.sku
|
38
|
-
|
39
|
-
@product.cents = '1'
|
40
|
-
assert_kind_of Integer, @product.cents
|
41
|
-
|
42
|
-
@product.related = '1'
|
43
|
-
assert_kind_of Array, @product.related
|
44
|
-
end
|
45
|
-
|
46
|
-
def test_default_values
|
47
|
-
assert_equal nil, @product.cents
|
48
|
-
assert_equal 'USD', @product.currency
|
49
|
-
assert_equal true, @product.in_stock
|
50
|
-
assert_equal [], @product.related
|
51
|
-
assert_kind_of Date, @product.created_on
|
52
|
-
end
|
53
|
-
|
54
|
-
def test_recursive_hashes
|
55
|
-
foo = Foo.new('bar' => { 'baz' => 1 })
|
56
|
-
hsh = foo.marshal_dump
|
57
|
-
foo.marshal_load(hsh)
|
58
|
-
assert_equal({ 'baz' => 1 }, foo.bar)
|
59
|
-
|
60
|
-
json = foo.to_json
|
61
|
-
assert foo, JSON.parse(json)
|
62
|
-
end
|
63
|
-
|
64
|
-
def test_recursive_array_handling
|
65
|
-
related = Product.new
|
66
|
-
@product.related << related
|
67
|
-
assert_equal [related], @product.related
|
68
|
-
assert_equal [], @product.related.first.related
|
69
|
-
end
|
70
|
-
|
71
|
-
def test_one_to_one
|
72
|
-
hsh = { :name => 'Manufacturer' }
|
73
|
-
|
74
|
-
@product.manufacturer = Structure.new(hsh)
|
75
|
-
assert_equal 'Manufacturer', @product.manufacturer.name
|
76
|
-
|
77
|
-
@product.manufacturer = hsh
|
78
|
-
assert_equal 'Manufacturer', @product.manufacturer.name
|
79
|
-
end
|
80
|
-
end
|