structure 0.15.1 → 0.16.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/.travis.yml CHANGED
@@ -6,3 +6,6 @@ rvm:
6
6
  - rbx
7
7
  - rbx-2.0
8
8
  - ree
9
+ branches:
10
+ only:
11
+ - master
data/Gemfile CHANGED
@@ -1,5 +1,14 @@
1
1
  source :rubygems
2
2
 
3
- gem 'activesupport', '>= 3.0'
3
+ gemspec
4
+
4
5
  gem 'json', :platform => [:mri_18, :jruby, :rbx]
5
6
  gem 'rake'
7
+
8
+ platforms :mri_18 do
9
+ gem 'ruby-debug', :require => 'ruby-debug' unless ENV['CI']
10
+ end
11
+
12
+ platforms :mri_19 do
13
+ gem 'ruby-debug19', :require => 'ruby-debug' unless ENV['CI']
14
+ end
data/README.md CHANGED
@@ -2,17 +2,27 @@
2
2
 
3
3
  [![travis](https://secure.travis-ci.org/hakanensari/structure.png)](http://travis-ci.org/hakanensari/structure)
4
4
 
5
- Structure is a Struct-like key/value container .
5
+ Structure is a typed, nestable key/value container.
6
6
 
7
- It will shine in the ephemeral landscape of API-backed data.
7
+ ## Usage
8
+
9
+ Install and require the gem.
8
10
 
9
11
  require 'structure'
10
12
 
11
- class Person < Structure
13
+ Define a model.
14
+
15
+ Document = Structure::Document
16
+
17
+ class Person < Document
12
18
  key :name
13
- many :friends
19
+ many :friends, :class_name => 'Person'
14
20
  end
15
21
 
22
+ person = Person.create(:name => 'John')
23
+ person.friends << Person.create(:name => 'Jane')
24
+ person.friends.size # 1
25
+
16
26
  Please see [the project page] [1] for more detailed info.
17
27
 
18
28
  [1]: http://code.papercavalier.com/structure/
@@ -0,0 +1,67 @@
1
+ require 'forwardable'
2
+
3
+ module Structure
4
+ class Collection < Array
5
+ class << self
6
+ attr :type
7
+
8
+ alias overridden_new new
9
+
10
+ def new(type)
11
+ unless type < Document
12
+ raise TypeError, "#{type} isn't a Document"
13
+ end
14
+ class_name = "#{type}Collection"
15
+
16
+ begin
17
+ class_name.constantize
18
+ rescue NameError
19
+ Object.class_eval <<-ruby
20
+ class #{class_name} < Structure::Collection
21
+ @type = #{type}
22
+ end
23
+ ruby
24
+ retry
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def inherited(child)
31
+ Kernel.send(:define_method, child.name) do |arg|
32
+ case arg
33
+ when child
34
+ arg
35
+ else
36
+ [arg].flatten.inject(child.new) { |a, e| a << e }
37
+ end
38
+ end
39
+ child.instance_eval { alias new overridden_new }
40
+ end
41
+ end
42
+
43
+ attr :members
44
+
45
+ %w{concat eql? push replace unshift}.each do |method|
46
+ define_method method do |ary|
47
+ super ary.map { |item| Kernel.send(type.to_s, item) }
48
+ end
49
+ end
50
+
51
+ def <<(item)
52
+ super Kernel.send(type.to_s, item)
53
+ end
54
+
55
+ def create(*args)
56
+ self.<< type.new(*args)
57
+
58
+ true
59
+ end
60
+
61
+ private
62
+
63
+ def type
64
+ self.class.type
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,66 @@
1
+ require 'yaml'
2
+
3
+ # When included in a document, this module will turn it into a static
4
+ # model which sources its records from a yaml file.
5
+ module Structure
6
+ class Document
7
+ module Static
8
+ class << self
9
+ private
10
+
11
+ def included(base)
12
+ base.key(:_id, Integer)
13
+ base.extend(ClassMethods)
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ include Enumerable
19
+
20
+ # The path for the data file.
21
+ #
22
+ # This file should contain a YAML representation of the records.
23
+ #
24
+ # Overwrite this reader with an opiniated location to dry.
25
+ attr :data_path
26
+
27
+ # Returns all records.
28
+ def all
29
+ @records ||= data.map do |record|
30
+ record['_id'] ||= record.delete('id') || increment_id
31
+ new(record)
32
+ end
33
+ end
34
+
35
+ # Yields each record to given block.
36
+ #
37
+ # Other enumerators will be made available by the Enumerable
38
+ # module.
39
+ def each(&block)
40
+ all.each { |record| block.call(record) }
41
+ end
42
+
43
+ # Finds a record by its ID.
44
+ def find(id)
45
+ detect { |record| record._id == id }
46
+ end
47
+
48
+ # Sets the path for the data file.
49
+ def set_data_path(data_path)
50
+ @data_path = data_path
51
+ end
52
+
53
+ private
54
+
55
+ def data
56
+ YAML.load_file(@data_path)
57
+ end
58
+
59
+
60
+ def increment_id
61
+ @increment_id = @increment_id.to_i + 1
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,212 @@
1
+ begin
2
+ JSON::JSON_LOADED
3
+ rescue NameError
4
+ require 'json'
5
+ end
6
+
7
+ require 'active_support/inflector'
8
+ require 'certainty'
9
+
10
+ require 'structure/collection'
11
+
12
+ module Structure
13
+ # A document is a typed, nestable key/value container.
14
+ #
15
+ # class Person < Document
16
+ # key :name
17
+ # key :age, Integer
18
+ # one :location
19
+ # many :friends, :class_name => 'Person'
20
+ # end
21
+ #
22
+ class Document
23
+ include Enumerable
24
+
25
+ autoload :Static,'structure/document/static'
26
+
27
+ # An attribute may be of the following data types.
28
+ TYPES = [Array, Boolean, Collection, Document, Float, Hash, Integer, String]
29
+
30
+ class << self
31
+ # Returns the default values for the attributes.
32
+ def defaults
33
+ @defaults ||= {}
34
+ end
35
+
36
+ # Builds a Ruby object out of the JSON representation of a
37
+ # structure.
38
+ def json_create(object)
39
+ object.delete 'json_class'
40
+ new object
41
+ end
42
+
43
+ # Defines an attribute.
44
+ #
45
+ # Takes a name, an optional type, and an optional hash of options.
46
+ #
47
+ # If nothing is specified, type defaults to +String+.
48
+ #
49
+ # Available options are:
50
+ #
51
+ # * +:default+, which sets the default value for the attribute. If
52
+ # no default value is specified, it defaults to +nil+.
53
+ def key(name, *args)
54
+ name = name.to_sym
55
+ options = args.last.is_a?(Hash) ? args.pop : {}
56
+ type = args.shift || String
57
+ default = options[:default]
58
+
59
+ if method_defined? name
60
+ raise NameError, "#{name} is already defined"
61
+ end
62
+
63
+ if (type.ancestors & TYPES).empty?
64
+ raise TypeError, "#{type} isn't a valid type"
65
+ end
66
+
67
+ if default.nil? || default.is_a?(type)
68
+ defaults[name] = default
69
+ else
70
+ raise TypeError, "#{default} isn't a #{type}"
71
+ end
72
+
73
+ module_eval do
74
+ define_method(name) { @attributes[name] }
75
+
76
+ define_method("#{name}=") do |value|
77
+ @attributes[name] = if value.is_a?(type) || value.nil?
78
+ value
79
+ else
80
+ Kernel.send(type.to_s, value)
81
+ end
82
+ end
83
+
84
+ alias_method "#{name}?", name if type == Boolean
85
+ end
86
+ end
87
+
88
+ # Defines an attribute that represents a collection.
89
+ def many(name, options = {})
90
+ class_name = options.delete(:class_name) || name.to_s.classify
91
+ klass = constantize class_name
92
+ collection = Collection.new(klass)
93
+
94
+ key name, collection, options.merge(:default => collection.new)
95
+ end
96
+
97
+ # Defines an attribute that represents another structure. Takes
98
+ # a name and optional hash of options.
99
+ def one(name, options = {})
100
+ class_name = options.delete(:class_name) || name.to_s.classify
101
+ klass = constantize class_name
102
+
103
+ unless klass < Document
104
+ raise TypeError, "#{klass} isn't a Document"
105
+ end
106
+
107
+ define_method("create_#{name}") do |*args|
108
+ self.send("#{name}=", klass.new(*args))
109
+ end
110
+
111
+ key name, klass, options
112
+ end
113
+
114
+ alias create new
115
+
116
+ private
117
+
118
+ def constantize(name)
119
+ name.constantize
120
+ rescue
121
+ Object.class_eval <<-ruby
122
+ class #{name} < Structure::Document; end
123
+ ruby
124
+ retry
125
+ end
126
+
127
+ def inherited(child)
128
+ Kernel.send(:define_method, child.name) do |arg|
129
+ case arg
130
+ when child
131
+ arg
132
+ when Hash
133
+ child.new arg
134
+ else
135
+ raise TypeError, "can't convert #{arg.class} into #{child}"
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ # Creates a new structure.
142
+ #
143
+ # A hash, if provided, will seed its attributes.
144
+ def initialize(hash = {})
145
+ @attributes = self.class.defaults.inject({}) do |a, (k, v)|
146
+ a[k] = v.is_a?(Array) || v.is_a?(Collection) ? v.dup : v
147
+ a
148
+ end
149
+
150
+ hash.each { |k, v| self.send("#{k}=", v) }
151
+ end
152
+
153
+ # The attributes that make up the structure.
154
+ attr :attributes
155
+
156
+ # Returns a Rails-friendly JSON representation of the structure.
157
+ def as_json(options = nil)
158
+ subset = if options
159
+ if attrs = options[:only]
160
+ @attributes.slice(*Array.wrap(attrs))
161
+ elsif attrs = options[:except]
162
+ @attributes.except(*Array.wrap(attrs))
163
+ else
164
+ @attributes.dup
165
+ end
166
+ else
167
+ @attributes.dup
168
+ end
169
+
170
+ klass = self.class.name
171
+ { JSON.create_id => klass }.
172
+ merge(subset)
173
+ end
174
+
175
+ # Calls block once for each attribute in the structure, passing that
176
+ # attribute as a parameter.
177
+ def each(&block)
178
+ @attributes.each { |v| block.call(v) }
179
+ end
180
+
181
+ # Returns a hash representation of the structure.
182
+ def to_hash
183
+ @attributes.inject({}) do |a, (k, v)|
184
+ a[k] =
185
+ if v.respond_to?(:to_hash)
186
+ v.to_hash
187
+ elsif v.is_a?(Array) || v.is_a?(Collection)
188
+ v.map { |e| e.respond_to?(:to_hash) ? e.to_hash : e }
189
+ else
190
+ v
191
+ end
192
+
193
+ a
194
+ end
195
+ end
196
+
197
+ # Returns a JSON representation of the structure.
198
+ def to_json(*args)
199
+ klass = self.class.name
200
+ { JSON.create_id => klass }.
201
+ merge(@attributes).
202
+ to_json(*args)
203
+ end
204
+
205
+ # Compares this object with another object for equality. A Structure
206
+ # is equal to the other object when latter is of the same class and
207
+ # the two objects' attributes are the same.
208
+ def ==(other)
209
+ other.is_a?(self.class) && @attributes == other.attributes
210
+ end
211
+ end
212
+ end
@@ -1,3 +1,3 @@
1
- class Structure
2
- VERSION = '0.15.1'
1
+ module Structure
2
+ VERSION = '0.16.0'
3
3
  end
data/lib/structure.rb CHANGED
@@ -1,189 +1 @@
1
- begin
2
- JSON::JSON_LOADED
3
- rescue NameError
4
- require 'json'
5
- end
6
-
7
- # Fabricate a +Boolean+ class.
8
- unless defined? Boolean
9
- module Boolean; end
10
- [TrueClass, FalseClass].each { |klass| klass.send :include, Boolean }
11
- end
12
-
13
- # = Structure
14
- #
15
- # Structure is a Struct-like key/value container.
16
- #
17
- # class Person < Structure
18
- # key :name
19
- # many :friends
20
- # end
21
- #
22
- class Structure
23
- include Enumerable
24
-
25
- autoload :Static, 'structure/static'
26
-
27
- # Available data type.
28
- TYPES = [Array, Boolean, Float, Hash, Integer, String, Structure]
29
-
30
- class << self
31
- # Defines an attribute that represents an array of objects.
32
- def many(name, options = {})
33
- key name, Array, { :default => [] }.merge(options)
34
- end
35
-
36
- # Defines an attribute that represents another structure.
37
- def one(name)
38
- key name, Structure
39
- end
40
-
41
- # Builds a structure out of its JSON representation.
42
- def json_create(object)
43
- object.delete 'json_class'
44
- new object
45
- end
46
-
47
- # Defines an attribute.
48
- #
49
- # Takes a name, an optional type, and an optional hash of options.
50
- #
51
- # The type can be +Array+, +Boolean+, +Float+, +Hash+, +Integer+,
52
- # +String+, a +Structure+, or a subclass thereof. If none is
53
- # specified, this defaults to +String+.
54
- #
55
- # Available options are:
56
- #
57
- # * +:default+, which sets the default value for the attribute.
58
- def key(name, *args)
59
- name = name.to_sym
60
- options = args.last.is_a?(Hash) ? args.pop : {}
61
- type = args.shift || String
62
- default = options[:default]
63
-
64
- if method_defined? name
65
- raise NameError, "#{name} is already defined"
66
- end
67
-
68
- if (type.ancestors & TYPES).empty?
69
- raise TypeError, "#{type} is not a valid type"
70
- end
71
-
72
- if default.nil? || default.is_a?(type)
73
- default_attributes[name] = default
74
- else
75
- msg = "#{default} isn't a#{'n' if type.name.match(/^[AI]/)} #{type}"
76
- raise TypeError, msg
77
- end
78
-
79
- module_eval do
80
- # A proc that typecasts value based on type.
81
- typecaster =
82
- case type.name
83
- when 'Boolean'
84
- lambda { |value|
85
- # This should take care of the different representations
86
- # of truth we might be feeding into the model.
87
- #
88
- # Any string other than "0" or "false" will evaluate to
89
- # true.
90
- #
91
- # Any integer other than 0 will evaluate to true.
92
- #
93
- # Otherwise, we do the double-bang trick to non-boolean
94
- # values.
95
- case value
96
- when Boolean
97
- value
98
- when String
99
- value !~ /0|false/i
100
- when Integer
101
- value != 0
102
- else
103
- !!value
104
- end
105
- }
106
- when /Hash|Structure/
107
- # We could possibly check if the value responds to #to_hash
108
- # and cast to hash if it does, but I don't see any use case
109
- # for this right now.
110
- lambda { |value|
111
- unless value.is_a? type
112
- raise TypeError, "#{value} is not a #{type}"
113
- end
114
- value
115
- }
116
- else
117
- lambda { |value| Kernel.send(type.to_s, value) }
118
- end
119
-
120
- # Define attribute accessors.
121
- define_method(name) { @attributes[name] }
122
-
123
- define_method("#{name}=") do |value|
124
- @attributes[name] = value.nil? ? nil : typecaster.call(value)
125
- end
126
- end
127
- end
128
-
129
- # Returns a hash of all attributes with default values.
130
- def default_attributes
131
- @default_attributes ||= {}
132
- end
133
- end
134
-
135
- # Creates a new structure.
136
- #
137
- # A hash, if provided, will seed its attributes.
138
- def initialize(hash = {})
139
- @attributes = {}
140
- self.class.default_attributes.each do |key, value|
141
- @attributes[key] = value.is_a?(Array) ? value.dup : value
142
- end
143
-
144
- hash.each { |key, value| self.send("#{key}=", value) }
145
- end
146
-
147
- # A hash that stores the attributes of the structure.
148
- attr :attributes
149
-
150
- # Returns a Rails-friendly JSON representation of the structure.
151
- def as_json(options = nil)
152
- subset = if options
153
- if attrs = options[:only]
154
- @attributes.slice(*Array.wrap(attrs))
155
- elsif attrs = options[:except]
156
- @attributes.except(*Array.wrap(attrs))
157
- else
158
- @attributes.dup
159
- end
160
- else
161
- @attributes.dup
162
- end
163
-
164
- klass = self.class.name
165
- { JSON.create_id => klass }.
166
- merge(subset)
167
- end
168
-
169
- # Calls block once for each attribute in the structure, passing that
170
- # attribute as a parameter.
171
- def each(&block)
172
- @attributes.each { |value| block.call(value) }
173
- end
174
-
175
- # Returns a JSON representation of the structure.
176
- def to_json(*args)
177
- klass = self.class.name
178
- { JSON.create_id => klass }.
179
- merge(@attributes).
180
- to_json(*args)
181
- end
182
-
183
- # Compares this object with another object for equality. A Structure
184
- # is equal to the other object when latter is of the same class and
185
- # the two objects' attributes are the same.
186
- def ==(other)
187
- other.is_a?(self.class) && @attributes == other.attributes
188
- end
189
- end
1
+ require 'structure/document'
data/structure.gemspec CHANGED
@@ -3,19 +3,23 @@ $:.push File.expand_path('../lib', __FILE__)
3
3
  require 'structure/version'
4
4
 
5
5
  Gem::Specification.new do |s|
6
- s.name = "structure"
6
+ s.name = 'structure'
7
7
  s.version = Structure::VERSION
8
8
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Hakan Ensari"]
10
- s.email = ["code@papercavalier.com"]
11
- s.homepage = "http://code.papercavalier.com/structure"
12
- s.summary = "A Struct-like key/value container"
13
- s.description = "Structure is a Struct-like key/value container."
9
+ s.authors = ['Hakan Ensari']
10
+ s.email = ['code@papercavalier.com']
11
+ s.homepage = 'http://github.com/hakanensari/structure'
12
+ s.summary = 'A typed, nestable key/value container'
13
+ s.description = 'A typed, nestable key/value container'
14
14
 
15
- s.rubyforge_project = "structure"
15
+ s.rubyforge_project = 'structure'
16
+
17
+ s.add_dependency 'certainty', '~> 0.2.0'
18
+ s.add_dependency 'activesupport', '~> 3.0'
19
+ s.add_dependency 'i18n', '~> 0.6.0'
16
20
 
17
21
  s.files = `git ls-files`.split("\n")
18
22
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
23
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
24
+ s.require_paths = ['lib']
21
25
  end
@@ -0,0 +1,33 @@
1
+ require File.expand_path('../helper.rb', __FILE__)
2
+
3
+ class Foo < Document
4
+ key :bar
5
+ end
6
+
7
+ class TestCollection < Test::Unit::TestCase
8
+ def setup
9
+ Structure::Collection.new(Foo)
10
+ end
11
+
12
+ def test_subclassing
13
+ assert FooCollection < Structure::Collection
14
+ assert_equal Foo, FooCollection.type
15
+ end
16
+
17
+ def test_conversion
18
+ item = Foo.new
19
+
20
+ assert_equal item, FooCollection([item]).first
21
+ assert_kind_of FooCollection, FooCollection([item])
22
+
23
+ assert_equal item, FooCollection(item).first
24
+ assert_kind_of FooCollection, FooCollection(item)
25
+
26
+ assert_raise(TypeError) { FooCollection('foo') }
27
+ end
28
+
29
+ def test_enumeration
30
+ assert_respond_to FooCollection.new, :map
31
+ assert_kind_of FooCollection, FooCollection.new.map! { |e| e }
32
+ end
33
+ end
@@ -0,0 +1,113 @@
1
+ require File.expand_path('../helper.rb', __FILE__)
2
+
3
+ class Person < Document
4
+ key :name
5
+ key :single, Boolean, :default => true
6
+ one :location
7
+ many :friends, :class_name => 'Person'
8
+ end
9
+
10
+ class Location < Document
11
+ key :lon, Float
12
+ key :lat, Float
13
+ end
14
+
15
+ class TestDocument < Test::Unit::TestCase
16
+ def test_enumeration
17
+ assert_respond_to Person.new, :map
18
+ end
19
+
20
+ def test_accessors
21
+ assert_respond_to Person.new, :name
22
+ assert_respond_to Person.new, :name=
23
+ end
24
+
25
+ def test_converter
26
+ assert_kind_of Person, Person(Person.new)
27
+ assert_kind_of Person, Person(:name => 'John')
28
+ assert_raise(TypeError) { Person('John') }
29
+ end
30
+
31
+ def test_errors
32
+ assert_raise(NameError) { Person.key :class }
33
+ assert_raise(TypeError) { Person.key :foo, Object }
34
+ assert_raise(TypeError) { Person.key :foo, :default => 1 }
35
+ end
36
+
37
+ def test_defaults
38
+ assert_equal true, Person.create.single?
39
+ end
40
+
41
+ def test_typecheck
42
+ location = Location.new
43
+
44
+ location.lon = '100'
45
+ assert_equal 100.0, location.lon
46
+
47
+ location.lon = nil
48
+ assert_nil location.lon
49
+ end
50
+
51
+ def test_one
52
+ person = Person.new
53
+
54
+ person.location = Location.new(:lon => 2.0)
55
+ assert_equal 2.0, person.location.lon
56
+
57
+ person.create_location :lon => 1.0
58
+ assert_equal 1.0, person.location.lon
59
+ end
60
+
61
+ def test_many
62
+ person = Person.new
63
+
64
+ person.friends.create
65
+ person.friends.create :name => 'John'
66
+ assert_equal 2, person.friends.size
67
+ assert_equal 0, person.friends.last.friends.size
68
+
69
+ friend = Person.new
70
+
71
+ person.friends = [friend]
72
+ assert_equal 1, person.friends.size
73
+ assert_equal 0, friend.friends.size
74
+
75
+ person.friends << friend
76
+ assert_equal 2, person.friends.size
77
+ assert_equal 0, friend.friends.size
78
+ assert_equal 0, person.friends.last.friends.size
79
+
80
+ person.friends.clear
81
+ assert_equal 0, person.friends.size
82
+ assert person.friends.empty?
83
+ end
84
+
85
+ def test_to_hash
86
+ person = Person.new
87
+ person.friends.create :name => 'John'
88
+ assert_equal 'John', person.to_hash[:friends].first[:name]
89
+ end
90
+
91
+ def test_json
92
+ person = Person.new
93
+ json = person.to_json
94
+ assert_equal person, JSON.parse(json)
95
+ end
96
+
97
+ def test_json_with_nested_structures
98
+ person = Person.new
99
+ person.friends << Person.new
100
+ person.location = Location.new
101
+ json = person.to_json
102
+ assert JSON.parse(json).friends.first.is_a? Person
103
+ assert JSON.parse(json).location.is_a? Location
104
+ end
105
+
106
+ def test_json_with_active_support
107
+ require 'active_support/ordered_hash'
108
+ require 'active_support/json'
109
+ person = Person.new
110
+ assert person.as_json(:only => :name).has_key?(:name)
111
+ assert !person.as_json(:except => :name).has_key?(:name)
112
+ end
113
+ end
data/test/helper.rb CHANGED
@@ -1,6 +1,5 @@
1
1
  $:.push File.expand_path('../../lib', __FILE__)
2
2
 
3
- require 'rubygems'
4
3
  require 'bundler/setup'
5
4
 
6
5
  begin
@@ -10,3 +9,5 @@ end
10
9
 
11
10
  require 'structure'
12
11
  require 'test/unit'
12
+
13
+ Document = Structure::Document
data/test/static_test.rb CHANGED
@@ -1,17 +1,17 @@
1
1
  require File.expand_path('../helper.rb', __FILE__)
2
2
 
3
- class City < Structure
3
+ class City < Document
4
4
  include Static
5
5
 
6
6
  key :name
7
7
  many :neighborhoods
8
8
  end
9
9
 
10
- class Neighborhood < Structure
10
+ class Neighborhood < Document
11
11
  key :name
12
12
  end
13
13
 
14
- class Dummy < Structure
14
+ class Dummy < Document
15
15
  include Static
16
16
 
17
17
  key :name
@@ -57,6 +57,6 @@ class TestStatic < Test::Unit::TestCase
57
57
 
58
58
  def test_nesting
59
59
  fixture City, 'cities_with_neighborhoods'
60
- # assert_kind_of Neighborhood, City.first.neighborhoods.first
60
+ assert_kind_of Neighborhood, City.first.neighborhoods.first
61
61
  end
62
62
  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.15.1
4
+ version: 0.16.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,9 +9,42 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-08-12 00:00:00.000000000Z
13
- dependencies: []
14
- description: Structure is a Struct-like key/value container.
12
+ date: 2011-08-24 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: certainty
16
+ requirement: &70198704238520 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70198704238520
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70198704237620 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70198704237620
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ requirement: &70198704236860 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.6.0
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70198704236860
47
+ description: A typed, nestable key/value container
15
48
  email:
16
49
  - code@papercavalier.com
17
50
  executables: []
@@ -20,22 +53,24 @@ extra_rdoc_files: []
20
53
  files:
21
54
  - .gitignore
22
55
  - .travis.yml
23
- - CHANGELOG.md
24
56
  - Gemfile
25
57
  - LICENSE
26
58
  - README.md
27
59
  - Rakefile
28
60
  - lib/structure.rb
29
- - lib/structure/static.rb
61
+ - lib/structure/collection.rb
62
+ - lib/structure/document.rb
63
+ - lib/structure/document/static.rb
30
64
  - lib/structure/version.rb
31
65
  - structure.gemspec
66
+ - test/collection_test.rb
67
+ - test/document_test.rb
32
68
  - test/fixtures/cities.yml
33
69
  - test/fixtures/cities_with_neighborhoods.yml
34
70
  - test/fixtures/cities_without_ids.yml
35
71
  - test/helper.rb
36
72
  - test/static_test.rb
37
- - test/structure_test.rb
38
- homepage: http://code.papercavalier.com/structure
73
+ homepage: http://github.com/hakanensari/structure
39
74
  licenses: []
40
75
  post_install_message:
41
76
  rdoc_options: []
@@ -49,23 +84,27 @@ required_ruby_version: !ruby/object:Gem::Requirement
49
84
  version: '0'
50
85
  segments:
51
86
  - 0
52
- hash: 2919723217978587663
87
+ hash: -2067006556322233036
53
88
  required_rubygems_version: !ruby/object:Gem::Requirement
54
89
  none: false
55
90
  requirements:
56
91
  - - ! '>='
57
92
  - !ruby/object:Gem::Version
58
93
  version: '0'
94
+ segments:
95
+ - 0
96
+ hash: -2067006556322233036
59
97
  requirements: []
60
98
  rubyforge_project: structure
61
99
  rubygems_version: 1.8.6
62
100
  signing_key:
63
101
  specification_version: 3
64
- summary: A Struct-like key/value container
102
+ summary: A typed, nestable key/value container
65
103
  test_files:
104
+ - test/collection_test.rb
105
+ - test/document_test.rb
66
106
  - test/fixtures/cities.yml
67
107
  - test/fixtures/cities_with_neighborhoods.yml
68
108
  - test/fixtures/cities_without_ids.yml
69
109
  - test/helper.rb
70
110
  - test/static_test.rb
71
- - test/structure_test.rb
data/CHANGELOG.md DELETED
@@ -1,39 +0,0 @@
1
- # CHANGELOG
2
-
3
- ## 0.15.0
4
- * bring back static module
5
-
6
- ## 0.14.0
7
- * rename .attribute back to .key
8
- * shorten .embeds_many and .embeds_one to .many and .one
9
- * remove presence methods
10
- * add options to .many
11
-
12
- ## 0.13.0
13
-
14
- * remove static module
15
- * rename .key to .attribute
16
-
17
- # 0.12.0
18
-
19
- * add static module
20
-
21
- # 0.11.0
22
-
23
- * .key now emulates DataMapper.property
24
-
25
- # 0.10.0
26
-
27
- * rename .has_one and .has_many to .embeds_one and .embeds_many to make room
28
- for associations
29
-
30
- # 0.9.0
31
-
32
- * add presence method
33
-
34
- # 0.8.0
35
-
36
- * make JSON patch compatible with Active Support
37
- * remove URI from list of types
38
-
39
- The rest is history.
@@ -1,61 +0,0 @@
1
- # When included in a structure, this module turns it into a static
2
- # model, the data of which is sourced from a yaml file.
3
- #
4
- # This is a basic implementation and does not handle nested structures.
5
- # See test.
6
- class Structure
7
- module Static
8
- def self.included(base)
9
- base.key(:_id, Integer)
10
- base.extend(ClassMethods)
11
- end
12
-
13
- module ClassMethods
14
- include Enumerable
15
-
16
- # The path for the data file.
17
- #
18
- # This file should contain a YAML representation of the records.
19
- #
20
- # Overwrite this reader with an opiniated location to dry.
21
- attr :data_path
22
-
23
- # Returns all records.
24
- def all
25
- @records ||= data.map do |record|
26
- record["_id"] ||= record.delete("id") || increment_id
27
- new(record)
28
- end
29
- end
30
-
31
- # Yields each record to given block.
32
- #
33
- # Other enumerators will be made available by the Enumerable
34
- # module.
35
- def each(&block)
36
- all.each { |record| block.call(record) }
37
- end
38
-
39
- # Finds a record by its ID.
40
- def find(id)
41
- detect { |record| record._id == id }
42
- end
43
-
44
- # Sets the path for the data file.
45
- def set_data_path(data_path)
46
- @data_path = data_path
47
- end
48
-
49
- private
50
-
51
- def data
52
- YAML.load_file(@data_path)
53
- end
54
-
55
-
56
- def increment_id
57
- @increment_id = @increment_id.to_i + 1
58
- end
59
- end
60
- end
61
- end
@@ -1,124 +0,0 @@
1
- require File.expand_path('../helper.rb', __FILE__)
2
-
3
- class Book < Structure
4
- key :title
5
- key :published, Boolean, :default => true
6
- key :pages, Integer
7
- end
8
-
9
- class Person < Structure
10
- key :name
11
- one :partner
12
- many :friends
13
- many :parents, :default => 2.times.map { Person.new }
14
- end
15
-
16
- class TestStructure < Test::Unit::TestCase
17
- def test_enumeration
18
- assert_respond_to Book.new, :map
19
- end
20
-
21
- def test_accessors
22
- book = Book.new
23
- assert_respond_to book, :title
24
- assert_respond_to book, :title=
25
- end
26
-
27
- def test_key_errors
28
- assert_raise(NameError) { Book.key :class }
29
- assert_raise(TypeError) { Book.key :foo, Object }
30
- assert_raise(TypeError) { Book.key :foo, :default => 1 }
31
- end
32
-
33
- def test_default_attributes
34
- exp = { :title => nil,
35
- :published => true,
36
- :pages => nil }
37
- assert_equal exp, Book.default_attributes
38
- end
39
-
40
- def test_initialization
41
- book = Book.new(:title => 'Foo', :pages => 100)
42
- assert_equal 'Foo', book.title
43
- assert_equal 100, book.pages
44
- end
45
-
46
- def test_typecasting
47
- book = Book.new
48
-
49
- book.pages = "100"
50
- assert_equal 100, book.pages
51
-
52
- book.pages = nil
53
- assert_nil book.pages
54
-
55
- book.title = 1
56
- book.title = '1'
57
- end
58
-
59
- def test_boolean_typecasting
60
- book = Book.new
61
-
62
- book.published = 'false'
63
- assert book.published == false
64
-
65
- book.published = 'FALSE'
66
- assert book.published == false
67
-
68
- book.published = '0'
69
- assert book.published == false
70
-
71
- book.published = 'foo'
72
- assert book.published == true
73
-
74
- book.published = 0
75
- assert book.published == false
76
-
77
- book.published = 10
78
- assert book.published == true
79
- end
80
-
81
- def test_defaults
82
- assert_equal nil, Book.new.title
83
- assert_equal true, Book.new.published
84
- assert_equal nil, Person.new.partner
85
- assert_equal [], Person.new.friends
86
- end
87
-
88
- def test_array
89
- person = Person.new
90
- friend = Person.new
91
- person.friends << person
92
- assert_equal 1, person.friends.count
93
- assert_equal 0, friend.friends.count
94
- end
95
-
96
- def test_many
97
- person = Person.new
98
- assert_equal 2, person.parents.size
99
- end
100
-
101
- def test_json
102
- book = Book.new(:title => 'Foo')
103
- json = book.to_json
104
- assert_equal book, JSON.parse(json)
105
- end
106
-
107
- def test_json_with_nested_structures
108
- person = Person.new
109
- person.friends << Person.new
110
- person.partner = Person.new
111
- json = person.to_json
112
- assert JSON.parse(json).friends.first.is_a? Person
113
- assert JSON.parse(json).partner.is_a? Person
114
- end
115
-
116
- def test_json_with_active_support
117
- require 'active_support/ordered_hash'
118
- require 'active_support/json'
119
-
120
- book = Book.new
121
- assert book.as_json(:only => :title).has_key?(:title)
122
- assert !book.as_json(:except => :title).has_key?(:title)
123
- end
124
- end