structure 0.12.2 → 0.13.1
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/.gitignore +1 -1
- data/.travis.yml +5 -0
- data/CHANGELOG.md +15 -10
- data/Gemfile +4 -3
- data/README.md +38 -86
- data/Rakefile +6 -7
- data/lib/structure.rb +73 -64
- data/lib/structure/version.rb +2 -2
- data/structure.gemspec +3 -14
- data/{spec/models/book.rb → test/models.rb} +5 -1
- data/test/structure_test.rb +134 -0
- metadata +15 -89
- data/benchmark.rb +0 -24
- data/lib/structure/static.rb +0 -52
- data/spec/fixtures/cities.yml +0 -5
- data/spec/fixtures/cities_with_neighborhoods.yml +0 -7
- data/spec/fixtures/cities_without_id.yml +0 -4
- data/spec/models/city.rb +0 -14
- data/spec/models/person.rb +0 -5
- data/spec/spec_helper.rb +0 -7
- data/spec/structure/json_spec.rb +0 -62
- data/spec/structure/static_spec.rb +0 -85
- data/spec/structure_spec.rb +0 -221
data/CHANGELOG.md
CHANGED
@@ -1,21 +1,26 @@
|
|
1
|
-
# 0.
|
1
|
+
# 0.13
|
2
2
|
|
3
|
-
*
|
4
|
-
*
|
3
|
+
* Remove static module.
|
4
|
+
* Rename .key to .attribute.
|
5
5
|
|
6
|
-
# 0.
|
6
|
+
# 0.12
|
7
7
|
|
8
|
-
* Add
|
8
|
+
* Add static module.
|
9
|
+
|
10
|
+
# 0.11
|
11
|
+
|
12
|
+
* .key now emulates DataMapper.property.
|
9
13
|
|
10
14
|
# 0.10
|
11
15
|
|
12
|
-
* Rename has_one and has_many to embeds_one and embeds_many to make room
|
16
|
+
* Rename .has_one and .has_many to .embeds_one and .embeds_many to make room
|
13
17
|
for associations.
|
14
18
|
|
15
|
-
# 0.
|
19
|
+
# 0.9
|
16
20
|
|
17
|
-
*
|
21
|
+
* Add presence method.
|
18
22
|
|
19
|
-
# 0.
|
23
|
+
# 0.8
|
20
24
|
|
21
|
-
*
|
25
|
+
* Make JSON patch compatible with Active Support.
|
26
|
+
* Remove URI from list of types.
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,101 +1,69 @@
|
|
1
|
-
Structure
|
2
|
-
=========
|
3
|
-
|
4
|
-
Structure is a key/value container for modeling ephemeral data in Ruby.
|
5
|
-
|
6
|
-
Structure typecasts, nests other structures, and talks flawless JSON.
|
7
|
-
|
8
|
-
#_ d
|
9
|
-
##_ d#
|
10
|
-
NN#p j0NN
|
11
|
-
40NNh_ _gN#B0
|
12
|
-
4JF@NNp_ _g0WNNL@
|
13
|
-
JLE5@WRNp_ _g@NNNF3_L
|
14
|
-
_F`@q4WBN@Np_ _gNN@ZL#p"Fj_
|
15
|
-
"0^#-LJ_9"NNNMp__ _gN#@#"R_#g@q^9"
|
16
|
-
a0,3_j_j_9FN@N@0NMp__ __ggNZNrNM"P_f_f_E,0a
|
17
|
-
j L 6 9""Q"#^q@NDNNNMpg____ ____gggNNW#W4p^p@jF"P"]"j F
|
18
|
-
rNrr4r*pr4r@grNr@q@Ng@q@N0@N#@NNMpmggggmqgNN@NN@#@4p*@M@p4qp@w@m@Mq@r#rq@r
|
19
|
-
F Jp 9__b__M,Juw*w*^#^9#""EED*dP_@EZ@^E@*#EjP"5M"gM@p*Ww&,jL_J__f F j
|
20
|
-
-r#^^0""E" 6 q q__hg-@4""*,_Z*q_"^pwr""p*C__@""0N-qdL_p" p J" 3""5^^0r-
|
21
|
-
t J __,Jb--N""", *_s0M`""q_a@NW__JP^u_p"""p4a,p" _F""V--wL,_F_ F #
|
22
|
-
_,Jp*^#""9 L 5_a*N"""q__INr" "q_e^"*,p^""qME_ y"""p6u,f j' f "N^--LL_
|
23
|
-
L ] k,w@#"""_ "_a*^E ba-" ^qj-""^pe" J^-u_f _f "q@w,j f jL
|
24
|
-
#_,J@^""p `_ _jp-""q _Dw^" ^cj*""*,j^ "p#_ y""^wE_ _F F"^qN,_j
|
25
|
-
w*^0 4 9__sAF" `L _Dr" m__m""q__a^"m__* "qA_ j" ""Au__f J 0^--
|
26
|
-
] J_,x-E 3_ jN^" `u _w^*_ _RR_ _J^w_ j" "pL_ f 7^-L_F #
|
27
|
-
jLs*^6 `_ _&*" q _,NF "wp" "*g" _NL_ p "-d_ F ]"*u_F
|
28
|
-
,x-"F ] Ax^" q hp" `u jM""u a^ ^, j" "*g_ p ^mg_ D.H. 1992
|
29
|
-
|
30
|
-
|
31
|
-
Usage
|
32
|
-
-----
|
33
|
-
|
34
|
-
Define a model:
|
1
|
+
# Structure
|
35
2
|
|
36
|
-
|
37
|
-
require 'structure'
|
3
|
+
[](http://travis-ci.org/papercavalier/structure)
|
38
4
|
|
39
|
-
class
|
40
|
-
|
41
|
-
|
42
|
-
embeds_many :friends
|
43
|
-
embeds_one :partner
|
44
|
-
end
|
45
|
-
```
|
5
|
+
Structure is Ruby module that turns a class into a key/value container.
|
6
|
+
|
7
|
+
## Usage
|
46
8
|
|
47
|
-
|
9
|
+
Set up models.
|
48
10
|
|
49
11
|
```ruby
|
50
|
-
|
51
|
-
```
|
12
|
+
require 'structure'
|
52
13
|
|
53
|
-
|
14
|
+
class Book
|
15
|
+
include Structure
|
54
16
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
17
|
+
attribute :title
|
18
|
+
attribute :binding, :default => "Hardcover"
|
19
|
+
attribute :year_published, Integer
|
20
|
+
embeds_many :authors
|
21
|
+
end
|
22
|
+
|
23
|
+
class Author
|
24
|
+
include Structure
|
25
|
+
|
26
|
+
attribute :name
|
27
|
+
attribute :role
|
28
|
+
end
|
59
29
|
```
|
60
30
|
|
61
|
-
|
31
|
+
Create some objects.
|
62
32
|
|
63
33
|
```ruby
|
64
|
-
|
65
|
-
=>
|
34
|
+
book = Book.new :title => "A Thousand Plateaus"
|
35
|
+
author = Author.new :name => "Gilles Deleuze"
|
36
|
+
book.authors << author
|
66
37
|
```
|
67
38
|
|
68
|
-
|
69
|
-
Embed other structures:
|
39
|
+
Attributes in structures are typecasted.
|
70
40
|
|
71
41
|
```ruby
|
72
|
-
|
73
|
-
|
42
|
+
book.year_published = "1985"
|
43
|
+
puts book.year_published
|
44
|
+
=> 1985
|
74
45
|
```
|
75
46
|
|
76
|
-
|
47
|
+
Translate to JSON and back into Ruby.
|
77
48
|
|
78
49
|
```ruby
|
79
|
-
|
50
|
+
json = book.to_json
|
51
|
+
puts json
|
52
|
+
=> {"json_class":"Book","title":"A Thousand Plateaus","binding":"Hardcover,"year_published":1985,"authors":[{"json_class":"Author","name":"Gilles Deleuze","role":null}]}
|
80
53
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
person = JSON.parse(json)
|
85
|
-
person.friends.first
|
86
|
-
=> #<Person:0x0000010107d030 @attributes={:name=>"John Doe", :age=>nil, :friends=>[], :partner=>nil}>
|
54
|
+
book = JSON.parse(json)
|
55
|
+
puts book.authors.first.name
|
56
|
+
=> "Gilles Deleuze"
|
87
57
|
```
|
88
58
|
|
89
|
-
|
59
|
+
Mix in Active Model modules.
|
90
60
|
|
91
61
|
```ruby
|
92
62
|
require 'active_model'
|
93
63
|
|
94
|
-
class Book
|
64
|
+
class Book
|
95
65
|
include ActiveModel::Validations
|
96
66
|
|
97
|
-
key :title
|
98
|
-
|
99
67
|
validates_presence_of :title
|
100
68
|
end
|
101
69
|
|
@@ -104,20 +72,4 @@ book.valid?
|
|
104
72
|
=> false
|
105
73
|
book.errors
|
106
74
|
=> {:title=>["can't be blank"]}
|
107
|
-
book.title = "Society of the Spectacle"
|
108
|
-
book.valid?
|
109
|
-
=> true
|
110
75
|
```
|
111
|
-
|
112
|
-
Types
|
113
|
-
-----
|
114
|
-
|
115
|
-
Structure supports the following types:
|
116
|
-
|
117
|
-
* Array
|
118
|
-
* Boolean
|
119
|
-
* Float
|
120
|
-
* Hash
|
121
|
-
* Integer
|
122
|
-
* String
|
123
|
-
* Structure
|
data/Rakefile
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
require 'bundler'
|
2
|
-
require '
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
3
|
|
4
|
-
|
4
|
+
task :default => :test
|
5
5
|
|
6
|
-
|
7
|
-
|
6
|
+
Rake::TestTask.new do |test|
|
7
|
+
test.libs << 'test'
|
8
|
+
test.test_files = FileList['test/**/*_test.rb']
|
8
9
|
end
|
9
|
-
|
10
|
-
task :default => [:spec]
|
data/lib/structure.rb
CHANGED
@@ -4,94 +4,95 @@ rescue NameError
|
|
4
4
|
require 'json'
|
5
5
|
end
|
6
6
|
|
7
|
-
#
|
8
|
-
unless
|
7
|
+
# Fabricate a +Boolean+ class.
|
8
|
+
unless defined? Boolean
|
9
9
|
module Boolean; end
|
10
|
-
|
11
|
-
class FalseClass; include Boolean; end
|
10
|
+
[TrueClass, FalseClass].each { |klass| klass.send :include, Boolean }
|
12
11
|
end
|
13
12
|
|
14
|
-
#
|
15
|
-
|
13
|
+
# Structure is a Ruby module that turns a class into a key/value container.
|
14
|
+
#
|
15
|
+
# class Person
|
16
|
+
# include Structure
|
17
|
+
#
|
18
|
+
# attribute :name
|
19
|
+
# attribute :age, Integer
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# person = Person.new(:name => "John")
|
23
|
+
# person.name
|
24
|
+
# => "John"
|
25
|
+
#
|
26
|
+
module Structure
|
16
27
|
include Enumerable
|
17
28
|
|
29
|
+
# Structure supports the following types.
|
18
30
|
TYPES = [Array, Boolean, Float, Hash, Integer, String, Structure]
|
19
31
|
|
20
|
-
|
21
|
-
#
|
22
|
-
#
|
23
|
-
def embeds_many(
|
24
|
-
key
|
32
|
+
module ClassMethods
|
33
|
+
# Defines an attribute that represents an array of objects, possibly
|
34
|
+
# structures.
|
35
|
+
def embeds_many(key)
|
36
|
+
attribute key, Array, :default => []
|
25
37
|
end
|
26
38
|
|
27
|
-
#
|
28
|
-
def embeds_one(
|
29
|
-
key
|
39
|
+
# Defines an attribute that represents another structure.
|
40
|
+
def embeds_one(key)
|
41
|
+
attribute key, Structure
|
30
42
|
end
|
31
43
|
|
44
|
+
# Builds a structure out of its JSON representation.
|
32
45
|
def json_create(object)
|
33
|
-
object.delete
|
34
|
-
new
|
46
|
+
object.delete 'json_class'
|
47
|
+
new object
|
35
48
|
end
|
36
49
|
|
37
|
-
# Defines an attribute
|
50
|
+
# Defines an attribute.
|
38
51
|
#
|
39
|
-
# Takes a
|
52
|
+
# Takes a key, an optional type, and an optional hash of options.
|
40
53
|
#
|
41
|
-
# The type can be Array
|
42
|
-
# Structure
|
54
|
+
# The type can be +Array+, +Boolean+, +Float+, +Hash+, +Integer+, +String+,
|
55
|
+
# or +Structure+. If none is specified, this defaults to String.
|
43
56
|
#
|
44
57
|
# Available options are:
|
45
58
|
#
|
46
|
-
# *
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
def key(name, *args)
|
53
|
-
name = name.to_sym
|
54
|
-
options = if args.last.is_a? Hash
|
55
|
-
args.pop
|
56
|
-
else
|
57
|
-
{}
|
58
|
-
end
|
59
|
-
type = args.shift || String
|
60
|
-
|
61
|
-
if method_defined?(name)
|
62
|
-
raise NameError, "#{name} is already defined"
|
63
|
-
end
|
59
|
+
# * +:default+, which sets the default value for the attribute.
|
60
|
+
def attribute(key, *args)
|
61
|
+
key = key.to_sym
|
62
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
63
|
+
type = args.shift || String
|
64
|
+
default = options[:default]
|
64
65
|
|
65
|
-
|
66
|
-
raise
|
66
|
+
if method_defined? key
|
67
|
+
raise NameError, "#{key} is already defined"
|
67
68
|
end
|
68
69
|
|
69
|
-
default
|
70
|
-
|
71
|
-
|
70
|
+
if TYPES.include?(type) && (default.nil? || default.is_a?(type))
|
71
|
+
default_attributes[key] = default
|
72
|
+
else
|
73
|
+
msg = "#{default} is not a#{'n' if type.to_s.match(/^[AI]/)} #{type}"
|
74
|
+
raise TypeError, msg
|
72
75
|
end
|
73
76
|
|
74
|
-
default_attributes[name] = default
|
75
|
-
|
76
77
|
module_eval do
|
77
|
-
|
78
|
-
# Define a proc to typecast value.
|
78
|
+
# Define a closure that typecasts value.
|
79
79
|
typecast =
|
80
80
|
if type == Boolean
|
81
81
|
lambda do |value|
|
82
82
|
case value
|
83
|
+
when Boolean
|
84
|
+
value
|
83
85
|
when String
|
84
|
-
|
86
|
+
value !~ /0|false/i
|
85
87
|
when Integer
|
86
88
|
value != 0
|
87
89
|
else
|
88
|
-
|
90
|
+
!!value
|
89
91
|
end
|
90
92
|
end
|
91
93
|
elsif [Hash, Structure].include? type
|
92
|
-
|
93
|
-
#
|
94
|
-
# Structure.
|
94
|
+
# Don't bother with typecasting attributes of type +Hash+ or
|
95
|
+
# +Structure+.
|
95
96
|
lambda do |value|
|
96
97
|
unless value.is_a? type
|
97
98
|
raise TypeError, "#{value} is not a #{type}"
|
@@ -102,16 +103,17 @@ class Structure
|
|
102
103
|
lambda { |value| Kernel.send(type.to_s, value) }
|
103
104
|
end
|
104
105
|
|
105
|
-
# Define
|
106
|
-
define_method(
|
106
|
+
# Define attribute accessors.
|
107
|
+
define_method(key) { @attributes[key] }
|
107
108
|
|
108
|
-
#
|
109
|
-
|
110
|
-
@attributes[name] = value.nil? ? nil : typecast.call(value)
|
109
|
+
define_method("#{key}=") do |value|
|
110
|
+
@attributes[key] = value.nil? ? nil : typecast.call(value)
|
111
111
|
end
|
112
112
|
|
113
|
-
# Define a
|
114
|
-
|
113
|
+
# Define a method to check for presence.
|
114
|
+
unless type == Array
|
115
|
+
define_method("#{key}?") { !!@attributes[key] }
|
116
|
+
end
|
115
117
|
end
|
116
118
|
end
|
117
119
|
|
@@ -121,23 +123,27 @@ class Structure
|
|
121
123
|
end
|
122
124
|
end
|
123
125
|
|
126
|
+
def self.included(base)
|
127
|
+
base.extend(ClassMethods)
|
128
|
+
end
|
129
|
+
|
124
130
|
# Creates a new structure.
|
125
131
|
#
|
126
|
-
#
|
127
|
-
def initialize(
|
132
|
+
# A hash, if provided, will seed its attributes.
|
133
|
+
def initialize(hash = {})
|
128
134
|
@attributes = {}
|
129
135
|
self.class.default_attributes.each do |key, value|
|
130
136
|
@attributes[key] = value.is_a?(Array) ? value.dup : value
|
131
137
|
end
|
132
138
|
|
133
|
-
|
139
|
+
hash.each { |key, value| self.send("#{key}=", value) }
|
134
140
|
end
|
135
141
|
|
136
|
-
# A hash of
|
142
|
+
# A hash that stores the attributes of the structure.
|
137
143
|
attr_reader :attributes
|
138
144
|
|
145
|
+
# Returns a Rails-friendly JSON representation of the structure.
|
139
146
|
def as_json(options = nil)
|
140
|
-
# create a subset of the attributes by applying :only or :except
|
141
147
|
subset = if options
|
142
148
|
if attrs = options[:only]
|
143
149
|
@attributes.slice(*Array.wrap(attrs))
|
@@ -155,6 +161,8 @@ class Structure
|
|
155
161
|
merge(subset)
|
156
162
|
end
|
157
163
|
|
164
|
+
# Calls block once for each attribute in the structure, passing that
|
165
|
+
# attribute as a parameter.
|
158
166
|
def each(&block)
|
159
167
|
@attributes.each { |value| block.call(value) }
|
160
168
|
end
|
@@ -164,6 +172,7 @@ class Structure
|
|
164
172
|
@attributes.keys
|
165
173
|
end
|
166
174
|
|
175
|
+
# Returns a JSON representation of the structure.
|
167
176
|
def to_json(*args)
|
168
177
|
klass = self.class.name
|
169
178
|
{ JSON.create_id => klass }.
|
data/lib/structure/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
|
2
|
-
VERSION = '0.
|
1
|
+
module Structure
|
2
|
+
VERSION = '0.13.1'
|
3
3
|
end
|
data/structure.gemspec
CHANGED
@@ -8,23 +8,12 @@ Gem::Specification.new do |s|
|
|
8
8
|
s.platform = Gem::Platform::RUBY
|
9
9
|
s.authors = ["Paper Cavalier"]
|
10
10
|
s.email = ["code@papercavalier.com"]
|
11
|
-
s.homepage = "http://
|
12
|
-
s.summary = "
|
13
|
-
s.description =
|
14
|
-
Structure is a key/value container for modeling ephemeral data.
|
15
|
-
END_OF_DESCRIPTION
|
11
|
+
s.homepage = "http://code.papercavalier.com/structure"
|
12
|
+
s.summary = "Turns a class into a key/value container"
|
13
|
+
s.description = "Structure turns a class into a key/value container."
|
16
14
|
|
17
15
|
s.rubyforge_project = "structure"
|
18
16
|
|
19
|
-
{
|
20
|
-
'activesupport' => '>= 3.0',
|
21
|
-
'rake' => '~> 0.9',
|
22
|
-
'rspec' => '~> 2.6',
|
23
|
-
'ruby-debug19' => '~> 0.11.6'
|
24
|
-
}.each do |lib, version|
|
25
|
-
s.add_development_dependency lib, version
|
26
|
-
end
|
27
|
-
|
28
17
|
s.files = `git ls-files`.split("\n")
|
29
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
30
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require File.expand_path("../../lib/structure", __FILE__)
|
6
|
+
|
7
|
+
class Person
|
8
|
+
include Structure
|
9
|
+
|
10
|
+
attribute :name
|
11
|
+
attribute :age, Integer
|
12
|
+
attribute :married, Boolean, :default => false
|
13
|
+
embeds_one :spouse
|
14
|
+
embeds_many :children
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestStructure < Test::Unit::TestCase
|
18
|
+
def test_enumeration
|
19
|
+
person = Person.new
|
20
|
+
assert_respond_to(person, :map)
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_method_generation
|
24
|
+
person = Person.new
|
25
|
+
assert_respond_to(person, :name)
|
26
|
+
assert_respond_to(person, :name=)
|
27
|
+
assert_respond_to(person, :name?)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_attribute_errors
|
31
|
+
assert_raise(NameError) { Person.attribute :class }
|
32
|
+
assert_raise(TypeError) { Person.attribute :foo, Object }
|
33
|
+
assert_raise(TypeError) { Person.attribute :foo, :default => 1 }
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_default_attributes
|
37
|
+
assert_equal({ :name => nil, :age => nil, :married => false, :spouse => nil, :children => [] }, Person.default_attributes)
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_initialization
|
41
|
+
person = Person.new(:name => 'John', :age => 28)
|
42
|
+
assert_equal('John', person.name)
|
43
|
+
assert_equal(28, person.age)
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_typecasting
|
47
|
+
person = Person.new
|
48
|
+
|
49
|
+
person.age = "28"
|
50
|
+
assert_equal(28, person.age)
|
51
|
+
|
52
|
+
person.age = nil
|
53
|
+
assert_nil(person.age)
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_presence
|
57
|
+
person = Person.new
|
58
|
+
|
59
|
+
person.married = nil
|
60
|
+
assert(!person.married?)
|
61
|
+
|
62
|
+
person.married = false
|
63
|
+
assert(!person.married?)
|
64
|
+
|
65
|
+
person.married = true
|
66
|
+
assert(person.married?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_default_type
|
70
|
+
person = Person.new
|
71
|
+
person.name = 1
|
72
|
+
assert(person.name.is_a? String)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_boolean_typecasting
|
76
|
+
person = Person.new
|
77
|
+
|
78
|
+
person.married = 'false'
|
79
|
+
assert(person.married == false)
|
80
|
+
|
81
|
+
person.married = 'FALSE'
|
82
|
+
assert(person.married == false)
|
83
|
+
|
84
|
+
person.married = '0'
|
85
|
+
assert(person.married == false)
|
86
|
+
|
87
|
+
person.married = 'foo'
|
88
|
+
assert(person.married == true)
|
89
|
+
|
90
|
+
person.married = 0
|
91
|
+
assert(person.married == false)
|
92
|
+
|
93
|
+
person.married = 10
|
94
|
+
assert(person.married == true)
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_defaults
|
98
|
+
person = Person.new
|
99
|
+
assert_equal(false, person.married)
|
100
|
+
assert_equal(nil, person.name)
|
101
|
+
assert_equal(nil, person.spouse)
|
102
|
+
assert_equal([], person.children)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_array
|
106
|
+
person = Person.new
|
107
|
+
child = Person.new
|
108
|
+
person.children << child
|
109
|
+
assert_equal(1, person.children.count)
|
110
|
+
assert_equal(0, child.children.count)
|
111
|
+
end
|
112
|
+
|
113
|
+
def test_json
|
114
|
+
person = Person.new(:name => 'Joe')
|
115
|
+
json = person.to_json
|
116
|
+
assert_equal(person, JSON.parse(json))
|
117
|
+
end
|
118
|
+
|
119
|
+
def test_json_with_nested_structures
|
120
|
+
person = Person.new
|
121
|
+
person.children << Person.new
|
122
|
+
json = person.to_json
|
123
|
+
assert(JSON.parse(json).children.first.is_a? Person)
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_json_with_active_support
|
127
|
+
require 'active_support/ordered_hash'
|
128
|
+
require 'active_support/json'
|
129
|
+
|
130
|
+
person = Person.new
|
131
|
+
assert(person.as_json(:only => :name).has_key?(:name))
|
132
|
+
assert(!person.as_json(:except => :name).has_key?(:name))
|
133
|
+
end
|
134
|
+
end
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 13
|
8
|
+
- 1
|
9
|
+
version: 0.13.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Paper Cavalier
|
@@ -14,67 +14,11 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2011-07-
|
17
|
+
date: 2011-07-26 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
|
-
dependencies:
|
20
|
-
|
21
|
-
|
22
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
-
none: false
|
24
|
-
requirements:
|
25
|
-
- - ">="
|
26
|
-
- !ruby/object:Gem::Version
|
27
|
-
segments:
|
28
|
-
- 3
|
29
|
-
- 0
|
30
|
-
version: "3.0"
|
31
|
-
type: :development
|
32
|
-
prerelease: false
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: rake
|
36
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
-
none: false
|
38
|
-
requirements:
|
39
|
-
- - ~>
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 0
|
43
|
-
- 9
|
44
|
-
version: "0.9"
|
45
|
-
type: :development
|
46
|
-
prerelease: false
|
47
|
-
version_requirements: *id002
|
48
|
-
- !ruby/object:Gem::Dependency
|
49
|
-
name: rspec
|
50
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
51
|
-
none: false
|
52
|
-
requirements:
|
53
|
-
- - ~>
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
segments:
|
56
|
-
- 2
|
57
|
-
- 6
|
58
|
-
version: "2.6"
|
59
|
-
type: :development
|
60
|
-
prerelease: false
|
61
|
-
version_requirements: *id003
|
62
|
-
- !ruby/object:Gem::Dependency
|
63
|
-
name: ruby-debug19
|
64
|
-
requirement: &id004 !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
|
-
requirements:
|
67
|
-
- - ~>
|
68
|
-
- !ruby/object:Gem::Version
|
69
|
-
segments:
|
70
|
-
- 0
|
71
|
-
- 11
|
72
|
-
- 6
|
73
|
-
version: 0.11.6
|
74
|
-
type: :development
|
75
|
-
prerelease: false
|
76
|
-
version_requirements: *id004
|
77
|
-
description: Structure is a key/value container for modeling ephemeral data.
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Structure turns a class into a key/value container.
|
78
22
|
email:
|
79
23
|
- code@papercavalier.com
|
80
24
|
executables: []
|
@@ -86,28 +30,19 @@ extra_rdoc_files: []
|
|
86
30
|
files:
|
87
31
|
- .gitignore
|
88
32
|
- .rvmrc
|
33
|
+
- .travis.yml
|
89
34
|
- CHANGELOG.md
|
90
35
|
- Gemfile
|
91
36
|
- LICENSE
|
92
37
|
- README.md
|
93
38
|
- Rakefile
|
94
|
-
- benchmark.rb
|
95
39
|
- lib/structure.rb
|
96
|
-
- lib/structure/static.rb
|
97
40
|
- lib/structure/version.rb
|
98
|
-
- spec/fixtures/cities.yml
|
99
|
-
- spec/fixtures/cities_with_neighborhoods.yml
|
100
|
-
- spec/fixtures/cities_without_id.yml
|
101
|
-
- spec/models/book.rb
|
102
|
-
- spec/models/city.rb
|
103
|
-
- spec/models/person.rb
|
104
|
-
- spec/spec_helper.rb
|
105
|
-
- spec/structure/json_spec.rb
|
106
|
-
- spec/structure/static_spec.rb
|
107
|
-
- spec/structure_spec.rb
|
108
41
|
- structure.gemspec
|
42
|
+
- test/models.rb
|
43
|
+
- test/structure_test.rb
|
109
44
|
has_rdoc: true
|
110
|
-
homepage: http://
|
45
|
+
homepage: http://code.papercavalier.com/structure
|
111
46
|
licenses: []
|
112
47
|
|
113
48
|
post_install_message:
|
@@ -120,7 +55,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
120
55
|
requirements:
|
121
56
|
- - ">="
|
122
57
|
- !ruby/object:Gem::Version
|
123
|
-
hash:
|
58
|
+
hash: -2785152293474576694
|
124
59
|
segments:
|
125
60
|
- 0
|
126
61
|
version: "0"
|
@@ -129,7 +64,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
129
64
|
requirements:
|
130
65
|
- - ">="
|
131
66
|
- !ruby/object:Gem::Version
|
132
|
-
hash: 2262708410600119149
|
133
67
|
segments:
|
134
68
|
- 0
|
135
69
|
version: "0"
|
@@ -139,15 +73,7 @@ rubyforge_project: structure
|
|
139
73
|
rubygems_version: 1.3.7
|
140
74
|
signing_key:
|
141
75
|
specification_version: 3
|
142
|
-
summary:
|
76
|
+
summary: Turns a class into a key/value container
|
143
77
|
test_files:
|
144
|
-
-
|
145
|
-
-
|
146
|
-
- spec/fixtures/cities_without_id.yml
|
147
|
-
- spec/models/book.rb
|
148
|
-
- spec/models/city.rb
|
149
|
-
- spec/models/person.rb
|
150
|
-
- spec/spec_helper.rb
|
151
|
-
- spec/structure/json_spec.rb
|
152
|
-
- spec/structure/static_spec.rb
|
153
|
-
- spec/structure_spec.rb
|
78
|
+
- test/models.rb
|
79
|
+
- test/structure_test.rb
|
data/benchmark.rb
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
require 'benchmark'
|
2
|
-
require 'ostruct'
|
3
|
-
require_relative 'lib/structure'
|
4
|
-
|
5
|
-
class Person1 < Structure
|
6
|
-
key :name
|
7
|
-
end
|
8
|
-
|
9
|
-
class Person2 < Struct.new(:name)
|
10
|
-
end
|
11
|
-
|
12
|
-
class Person3
|
13
|
-
attr_accessor :name
|
14
|
-
end
|
15
|
-
|
16
|
-
n = 100000
|
17
|
-
Benchmark.bm do |x|
|
18
|
-
x.report('Structure') { n.times { Person1.new(:name => 'John') } }
|
19
|
-
x.report('Structure') { n.times { Person1.new.name = 'John' } }
|
20
|
-
x.report('OpenStruct') { n.times { OpenStruct.new(:name => 'John') } }
|
21
|
-
x.report('Struct') { n.times { Person2.new('John') } }
|
22
|
-
x.report('Class') { n.times { Person3.new.name = 'John' } }
|
23
|
-
x.report('Hash') { n.times { { name: 'John' } } }
|
24
|
-
end
|
data/lib/structure/static.rb
DELETED
@@ -1,52 +0,0 @@
|
|
1
|
-
require 'yaml'
|
2
|
-
|
3
|
-
class Structure
|
4
|
-
|
5
|
-
# An enumerable static structure, sourced from a yaml file.
|
6
|
-
module Static
|
7
|
-
def self.included(base)
|
8
|
-
base.key(:id, Integer)
|
9
|
-
base.extend(ClassMethods)
|
10
|
-
end
|
11
|
-
|
12
|
-
module ClassMethods
|
13
|
-
include Enumerable
|
14
|
-
|
15
|
-
attr_accessor :data_path
|
16
|
-
|
17
|
-
def all
|
18
|
-
@all ||= data.map do |record|
|
19
|
-
record["id"] ||= increment_id
|
20
|
-
new(record)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def each(&block)
|
25
|
-
all.each { |record| block.call(record) }
|
26
|
-
end
|
27
|
-
|
28
|
-
def find(id)
|
29
|
-
detect { |record| record.id == id }
|
30
|
-
end
|
31
|
-
|
32
|
-
def set_data_path(data_path)
|
33
|
-
@data_path = data_path
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def data
|
39
|
-
YAML.load_file(data_path)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Overwrite this method with an opiniated location to dry if necessary.
|
43
|
-
def data_path
|
44
|
-
@data_path
|
45
|
-
end
|
46
|
-
|
47
|
-
def increment_id
|
48
|
-
@increment_id = @increment_id.to_i + 1
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|
data/spec/fixtures/cities.yml
DELETED
data/spec/models/city.rb
DELETED
data/spec/models/person.rb
DELETED
data/spec/spec_helper.rb
DELETED
data/spec/structure/json_spec.rb
DELETED
@@ -1,62 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
shared_examples_for "a JSON interface" do
|
4
|
-
it "dumps to JSON" do
|
5
|
-
person.to_json.should eql json
|
6
|
-
end
|
7
|
-
|
8
|
-
it "loads from JSON" do
|
9
|
-
JSON.parse(json).should == person
|
10
|
-
end
|
11
|
-
|
12
|
-
context "when nesting other structures" do
|
13
|
-
before do
|
14
|
-
person.friends = [Person.new(:name => 'Jane')]
|
15
|
-
end
|
16
|
-
|
17
|
-
it "loads them into their corresponding structures" do
|
18
|
-
json = person.to_json
|
19
|
-
JSON.parse(json).friends.first.should be_a Person
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe Structure do
|
25
|
-
let(:person) { Person.new(:name => 'Joe') }
|
26
|
-
let(:json) { '{"json_class":"Person","name":"Joe","age":null,"friends":[]}' }
|
27
|
-
|
28
|
-
context "without Active Support" do
|
29
|
-
it_behaves_like "a JSON interface"
|
30
|
-
end
|
31
|
-
|
32
|
-
context "with Active Support" do
|
33
|
-
before(:all) do
|
34
|
-
require 'active_support/ordered_hash'
|
35
|
-
require 'active_support/json'
|
36
|
-
end
|
37
|
-
|
38
|
-
after(:all) do
|
39
|
-
Object.send(:remove_const, :ActiveSupport)
|
40
|
-
end
|
41
|
-
|
42
|
-
it_behaves_like "a JSON interface"
|
43
|
-
|
44
|
-
describe "#as_json" do
|
45
|
-
it "returns a hash" do
|
46
|
-
person.as_json.should be_a Hash
|
47
|
-
end
|
48
|
-
|
49
|
-
it "selects a subset of attributes" do
|
50
|
-
as_json = person.as_json(:only => :name)
|
51
|
-
as_json.should have_key :name
|
52
|
-
as_json.should_not have_key :age
|
53
|
-
end
|
54
|
-
|
55
|
-
it "rejects a subset of attributes" do
|
56
|
-
as_json = person.as_json(:except => :name)
|
57
|
-
as_json.should_not have_key :name
|
58
|
-
as_json.should have_key :age
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
@@ -1,85 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
class Structure
|
4
|
-
describe "A static structure" do
|
5
|
-
def replace_fixture(new_path)
|
6
|
-
City.instance_variable_set(:@all, nil)
|
7
|
-
fixture = File.expand_path("../../fixtures/#{new_path}", __FILE__)
|
8
|
-
City.set_data_path(fixture)
|
9
|
-
end
|
10
|
-
|
11
|
-
it "is enumerable" do
|
12
|
-
City.should be_an Enumerable
|
13
|
-
end
|
14
|
-
|
15
|
-
describe ".all" do
|
16
|
-
it "returns all records" do
|
17
|
-
City.all.should have(2).cities
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
describe ".find" do
|
22
|
-
it "finds a record by its id" do
|
23
|
-
City.find(1).should be_a City
|
24
|
-
end
|
25
|
-
|
26
|
-
it "returns nil if the record does not exist" do
|
27
|
-
City.find(4).should be_nil
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
describe ".set_data_path" do
|
32
|
-
it "sets the data path" do
|
33
|
-
City.set_data_path("foo")
|
34
|
-
City.send(:data_path).should eql "foo"
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
context "when sourcing data without ids" do
|
39
|
-
before(:all) do
|
40
|
-
@old_path = City.instance_variable_get(:@data_path)
|
41
|
-
replace_fixture("cities_without_id.yml")
|
42
|
-
end
|
43
|
-
|
44
|
-
after(:all) do
|
45
|
-
replace_fixture(@old_path)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "should auto-increment the ids of loaded records" do
|
49
|
-
City.map(&:id).should =~ [1, 2, 3]
|
50
|
-
end
|
51
|
-
|
52
|
-
describe ".increment_id" do
|
53
|
-
before do
|
54
|
-
City.instance_variable_set(:@increment_id, nil)
|
55
|
-
end
|
56
|
-
|
57
|
-
it "starts from 1" do
|
58
|
-
City.send(:increment_id).should eql 1
|
59
|
-
end
|
60
|
-
|
61
|
-
it "auto-increments" do
|
62
|
-
City.send(:increment_id)
|
63
|
-
City.send(:increment_id).should eql 2
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
context "when sourcing nested models" do
|
69
|
-
before(:all) do
|
70
|
-
@old_path = City.instance_variable_get(:@data_path)
|
71
|
-
replace_fixture("cities_with_neighborhoods.yml")
|
72
|
-
end
|
73
|
-
|
74
|
-
after(:all) do
|
75
|
-
replace_fixture(@old_path)
|
76
|
-
end
|
77
|
-
|
78
|
-
it "loads nested models" do
|
79
|
-
pending
|
80
|
-
neighborhoods = City.first.neighborhoods
|
81
|
-
neighborhoods.first.should be_a Neighborhood
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
data/spec/structure_spec.rb
DELETED
@@ -1,221 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
describe Structure do
|
4
|
-
let(:person) { Person.new }
|
5
|
-
|
6
|
-
it "is enumerable" do
|
7
|
-
person.should respond_to :map
|
8
|
-
end
|
9
|
-
|
10
|
-
describe ".key" do
|
11
|
-
it "defines accessors" do
|
12
|
-
%w{name name=}.each { |method| person.should respond_to method }
|
13
|
-
end
|
14
|
-
|
15
|
-
context "when a key name clashes with a method name" do
|
16
|
-
it "raises an error" do
|
17
|
-
expect do
|
18
|
-
Person.key :class
|
19
|
-
end.to raise_error NameError
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context "when an invalid type is specified" do
|
24
|
-
it "raises an error" do
|
25
|
-
expect do
|
26
|
-
Person.key :location, Object
|
27
|
-
end.to raise_error TypeError
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
context "when default value is not of the specified type" do
|
32
|
-
it "raises an error" do
|
33
|
-
expect do
|
34
|
-
Person.key :location, String, :default => 0
|
35
|
-
end.to raise_error TypeError
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
describe ".default_attributes" do
|
41
|
-
it "returns the default attributes for the structure" do
|
42
|
-
Person.send(:default_attributes).should == { :name => nil,
|
43
|
-
:age => nil,
|
44
|
-
:friends => [] }
|
45
|
-
Book.send(:default_attributes).should == { :title => nil,
|
46
|
-
:authors => nil }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "attribute getter" do
|
51
|
-
it "returns the value of the attribute" do
|
52
|
-
person.instance_variable_get(:@attributes)[:name] = 'Joe'
|
53
|
-
person.name.should eql 'Joe'
|
54
|
-
end
|
55
|
-
|
56
|
-
context "when type is Array and default value is []" do
|
57
|
-
let(:friend) { Person.new }
|
58
|
-
|
59
|
-
it "supports the `<<' idiom" do
|
60
|
-
person.friends << friend
|
61
|
-
person.friends.count.should eql 1
|
62
|
-
friend.friends.count.should eql 0
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
describe "attribute setter" do
|
68
|
-
it "sets the value of the attribute" do
|
69
|
-
person.name = "Joe"
|
70
|
-
person.instance_variable_get(:@attributes)[:name].should eql 'Joe'
|
71
|
-
end
|
72
|
-
|
73
|
-
context "when a type is specified" do
|
74
|
-
it "casts the value" do
|
75
|
-
person.age = "28"
|
76
|
-
person.age.should be_an Integer
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
context "when a type is not specified" do
|
81
|
-
it "casts to String" do
|
82
|
-
person.name = 123
|
83
|
-
person.name.should be_a String
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
87
|
-
context "when type is Boolean" do
|
88
|
-
context "when default value is true" do
|
89
|
-
it "does not raise an invalid type error" do
|
90
|
-
expect do
|
91
|
-
Person.key :single, Boolean, :default => true
|
92
|
-
end.not_to raise_error
|
93
|
-
end
|
94
|
-
end
|
95
|
-
|
96
|
-
context "when default value is false" do
|
97
|
-
it "does not raise an invalid type error" do
|
98
|
-
expect do
|
99
|
-
Person.key :married, Boolean, :default => false
|
100
|
-
end.not_to raise_error
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
context "when typecasting a set value" do
|
105
|
-
before(:all) do
|
106
|
-
Person.key :vegetarian, Boolean
|
107
|
-
end
|
108
|
-
|
109
|
-
it "typecasts 'true' to true" do
|
110
|
-
person.vegetarian = 'true'
|
111
|
-
person.vegetarian.should be_true
|
112
|
-
end
|
113
|
-
|
114
|
-
it "typecasts 'TRUE' to true" do
|
115
|
-
person.vegetarian = 'TRUE'
|
116
|
-
person.vegetarian.should be_true
|
117
|
-
end
|
118
|
-
|
119
|
-
it "typecasts '1' to true" do
|
120
|
-
person.vegetarian = '1'
|
121
|
-
person.vegetarian.should be_true
|
122
|
-
end
|
123
|
-
|
124
|
-
it "typecasts all other strings to false" do
|
125
|
-
person.vegetarian = 'foo'
|
126
|
-
person.vegetarian.should be_false
|
127
|
-
end
|
128
|
-
|
129
|
-
it "typecasts 0 to false" do
|
130
|
-
person.vegetarian = 0
|
131
|
-
person.vegetarian.should be_false
|
132
|
-
end
|
133
|
-
|
134
|
-
it "typecasts all other integers to true" do
|
135
|
-
person.vegetarian = 1
|
136
|
-
person.vegetarian.should be_true
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
context "when type is Hash" do
|
142
|
-
before(:all) do
|
143
|
-
Person.key :education, Hash
|
144
|
-
end
|
145
|
-
|
146
|
-
context "when setting to a value that is not a Hash" do
|
147
|
-
it "raises an error" do
|
148
|
-
expect do
|
149
|
-
person.education = 'foo'
|
150
|
-
end.to raise_error TypeError
|
151
|
-
end
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
context "when type is Structure" do
|
156
|
-
before(:all) do
|
157
|
-
Person.embeds_one :father
|
158
|
-
end
|
159
|
-
|
160
|
-
context "when setting to a value that is not a Structure" do
|
161
|
-
it "raises an error" do
|
162
|
-
expect do
|
163
|
-
person.father = 'foo'
|
164
|
-
end.to raise_error TypeError
|
165
|
-
end
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
context "when a default is specified" do
|
170
|
-
it "defaults to that value" do
|
171
|
-
Person.key :location, :default => 'New York'
|
172
|
-
person.location.should eql 'New York'
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
context "when a default is not specified" do
|
177
|
-
it "defaults to nil" do
|
178
|
-
person.age.should be_nil
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
context "when setting the value of an attribute to nil" do
|
183
|
-
it "does not typecast the value" do
|
184
|
-
person.age = nil
|
185
|
-
person.age.should be_a NilClass
|
186
|
-
end
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
describe "attribute presence" do
|
191
|
-
context "when the value of an attribute is nil" do
|
192
|
-
it "returns false" do
|
193
|
-
person.name?.should be_false
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
context "when the value of an attribute is false" do
|
198
|
-
it "returns false" do
|
199
|
-
person.instance_variable_get(:@attributes)[:name] = false
|
200
|
-
person.name?.should be_false
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
context "when the value of an attribute is set to a value other than false or nil" do
|
205
|
-
it "returns true" do
|
206
|
-
person.instance_variable_get(:@attributes)[:name] = "John"
|
207
|
-
person.name?.should be_true
|
208
|
-
end
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
describe ".new" do
|
213
|
-
context "when attributes are specified" do
|
214
|
-
it "initializes the object with those attributes" do
|
215
|
-
jane = Person.new(:name => 'Jane', :age => "29")
|
216
|
-
jane.name.should eql 'Jane'
|
217
|
-
jane.age.should eql 29
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|