structure 0.24.0 → 0.25.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|