structure 0.16.0 → 0.17.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -2,13 +2,11 @@ source :rubygems
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'json', :platform => [:mri_18, :jruby, :rbx]
5
+ gem 'activesupport', '~> 3.0'
6
+ gem 'json', :platform => [:mri_18, :jruby, :rbx]
6
7
  gem 'rake'
7
8
 
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']
9
+ unless ENV['CI']
10
+ gem 'ruby-debug', :platforms => :mri_18
11
+ gem 'ruby-debug19', :platforms => :mri_19
14
12
  end
data/README.md CHANGED
@@ -4,25 +4,11 @@
4
4
 
5
5
  Structure is a typed, nestable key/value container.
6
6
 
7
- ## Usage
8
-
9
- Install and require the gem.
10
-
11
- require 'structure'
12
-
13
- Define a model.
14
-
15
- Document = Structure::Document
16
-
17
- class Person < Document
18
- key :name
19
- many :friends, :class_name => 'Person'
7
+ class Person < Structure
8
+ key :name, String
9
+ many :friends
20
10
  end
21
11
 
22
- person = Person.create(:name => 'John')
23
- person.friends << Person.create(:name => 'Jane')
24
- person.friends.size # 1
25
-
26
12
  Please see [the project page] [1] for more detailed info.
27
13
 
28
14
  [1]: http://code.papercavalier.com/structure/
@@ -0,0 +1,19 @@
1
+ class Structure
2
+ # Converts structure to a JSON representation.
3
+ def as_json(options = nil)
4
+ subset = if options
5
+ if only = options[:only]
6
+ attributes.slice(*Array.wrap(only))
7
+ elsif except = options[:except]
8
+ attributes.except(*Array.wrap(except))
9
+ else
10
+ attributes.dup
11
+ end
12
+ else
13
+ attributes.dup
14
+ end
15
+
16
+ { JSON.create_id => self.class.name }.
17
+ merge(subset)
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ require 'yaml'
2
+
3
+ # This module provides the class methods that render a structure
4
+ # static, where records are sourced from a YAML file.
5
+ class Structure
6
+ module Static
7
+ include Enumerable
8
+
9
+ def self.extended(base)
10
+ base.key(:_id, Integer)
11
+ end
12
+
13
+ # The data file path.
14
+ attr :data_path
15
+
16
+ # Returns all records.
17
+ def all
18
+ @all ||= YAML.load_file(data_path).map do |hsh|
19
+ hsh['_id'] ||= hsh.delete('id') || hsh.delete('ID') || incr_id
20
+ new(hsh)
21
+ end
22
+ end
23
+
24
+ # Yields each record to given block.
25
+ def each(&block)
26
+ all.each { |item| block.call(item) }
27
+ end
28
+
29
+ # Finds a record by its ID.
30
+ def find(id)
31
+ super() { |item| item._id == id }
32
+ end
33
+
34
+ private
35
+
36
+ def incr_id
37
+ @id_cnt = @id_cnt.to_i + 1
38
+ end
39
+ end
40
+ end
@@ -1,3 +1,3 @@
1
- module Structure
2
- VERSION = '0.16.0'
1
+ class Structure
2
+ VERSION = '0.17.1'
3
3
  end
data/lib/structure.rb CHANGED
@@ -1 +1,153 @@
1
- require 'structure/document'
1
+ begin
2
+ JSON::JSON_LOADED
3
+ rescue NameError
4
+ require 'json'
5
+ end
6
+
7
+ # A structure is a nestable key/value container.
8
+ #
9
+ # class Person < Structure
10
+ # key :name
11
+ # key :age, Integer
12
+ # many :friends
13
+ # end
14
+ #
15
+ class Structure
16
+ include Enumerable
17
+
18
+ autoload :Static,'structure/static'
19
+
20
+ class << self
21
+ # Returns attribute keys and their default values.
22
+ def defaults
23
+ @defaults ||= {}
24
+ end
25
+
26
+ # Builds a structure out of the JSON representation of a
27
+ # structure.
28
+ def json_create(hsh)
29
+ hsh.delete 'json_class'
30
+ new hsh
31
+ end
32
+
33
+ # Defines an attribute.
34
+ #
35
+ # Takes a name and, optionally, a type and options hash.
36
+ #
37
+ # The type should be a Ruby class.
38
+ #
39
+ # Available options are:
40
+ #
41
+ # * +:default+, which specifies a default value for the attribute.
42
+ def key(name, *args)
43
+ name = name.to_sym
44
+ options = args.last.is_a?(Hash) ? args.pop : {}
45
+ type = args.shift
46
+ default = options[:default]
47
+
48
+ if method_defined?(name)
49
+ raise NameError, "#{name} is taken"
50
+ end
51
+
52
+ unless type.nil? || type.is_a?(Class)
53
+ raise TypeError, "#{type} isn't a Class"
54
+ end
55
+
56
+ if default.nil? || default.is_a?(type)
57
+ defaults[name] = default
58
+ else
59
+ raise TypeError, "#{default} isn't a #{type}"
60
+ end
61
+
62
+ define_method(name) { attributes[name] }
63
+
64
+ if type.nil?
65
+ define_method("#{name}=") { |val| attributes[name] = val }
66
+ elsif Kernel.respond_to? type.to_s
67
+ define_method("#{name}=") do |val|
68
+ attributes[name] =
69
+ if val.nil? || val.is_a?(type)
70
+ val
71
+ else
72
+ Kernel.send(type.to_s, val)
73
+ end
74
+ end
75
+ else
76
+ define_method("#{name}=") do |val|
77
+ attributes[name] =
78
+ if val.nil? || val.is_a?(type)
79
+ val
80
+ else
81
+ raise TypeError, "#{val} isn't a #{type}"
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ # A shorthand that defines an attribute that is an array.
88
+ def many(name)
89
+ key name, Array, :default => []
90
+ end
91
+
92
+ # Renders the structure static by setting the path for a YAML file
93
+ # that stores the records.
94
+ def set_data_file(path)
95
+ extend Static unless self.respond_to? :all
96
+
97
+ @data_path = path
98
+ end
99
+ end
100
+
101
+ # Creates a new structure.
102
+ #
103
+ # A hash, if provided, will seed the attributes.
104
+ def initialize(hsh = {})
105
+ @attributes = self.class.defaults.inject({}) do |a, (k, v)|
106
+ a[k] = v.is_a?(Array) ? v.dup : v
107
+ a
108
+ end
109
+
110
+ hsh.each { |k, v| self.send("#{k}=", v) }
111
+ end
112
+
113
+ # The attributes that make up the structure.
114
+ attr :attributes
115
+
116
+ # Calls block once for each attribute in the structure, passing that
117
+ # attribute as a parameter.
118
+ def each(&block)
119
+ attributes.each { |v| block.call(v) }
120
+ end
121
+
122
+ # Converts structure to a hash.
123
+ def to_hash
124
+ attributes.inject({}) do |a, (k, v)|
125
+ a[k] =
126
+ if v.respond_to? :to_hash
127
+ v.to_hash
128
+ elsif v.is_a? Array
129
+ v.map { |e| e.respond_to?(:to_hash) ? e.to_hash : e }
130
+ else
131
+ v
132
+ end
133
+
134
+ a
135
+ end
136
+ end
137
+
138
+ # Converts structure to a JSON representation.
139
+ def to_json(*args)
140
+ { JSON.create_id => self.class.name }.
141
+ merge(attributes).
142
+ to_json(*args)
143
+ end
144
+
145
+ # Compares this object with another object for equality. A Structure
146
+ # is equal to the other object when both are of the same class and
147
+ # the their attributes are the same.
148
+ def ==(other)
149
+ other.is_a?(self.class) && attributes == other.attributes
150
+ end
151
+ end
152
+
153
+ require 'structure/rails' if defined?(Rails)
data/structure.gemspec CHANGED
@@ -10,14 +10,10 @@ Gem::Specification.new do |s|
10
10
  s.email = ['code@papercavalier.com']
11
11
  s.homepage = 'http://github.com/hakanensari/structure'
12
12
  s.summary = 'A typed, nestable key/value container'
13
- s.description = 'A typed, nestable key/value container'
13
+ s.description = 'Structure is a typed, nestable key/value container.'
14
14
 
15
15
  s.rubyforge_project = 'structure'
16
16
 
17
- s.add_dependency 'certainty', '~> 0.2.0'
18
- s.add_dependency 'activesupport', '~> 3.0'
19
- s.add_dependency 'i18n', '~> 0.6.0'
20
-
21
17
  s.files = `git ls-files`.split("\n")
22
18
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -0,0 +1,140 @@
1
+ $:.push File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'bundler/setup'
4
+
5
+ begin
6
+ require 'ruby-debug'
7
+ rescue LoadError
8
+ end
9
+
10
+ require 'structure'
11
+ require 'test/unit'
12
+
13
+ class Test::Unit::TestCase
14
+ def self.test(name, &block)
15
+ test_name = "test_#{name.gsub(/\s+/,'_')}".to_sym
16
+ if method_defined? test_name
17
+ raise "#{test_name} is already defined in #{self}"
18
+ end
19
+ define_method test_name, &block
20
+ end
21
+ end
22
+
23
+ class Person < Structure
24
+ key :name
25
+ key :age, Integer
26
+ many :friends
27
+ end
28
+
29
+ class TestStructure < Test::Unit::TestCase
30
+ test "should enumerate" do
31
+ assert_respond_to Person.new, :map
32
+ end
33
+
34
+ test "should define accessors" do
35
+ assert_respond_to Person.new, :name
36
+ assert_respond_to Person.new, :name=
37
+ end
38
+
39
+ test "should raise errors" do
40
+ assert_raise(NameError) { Person.key :class }
41
+ assert_raise(TypeError) { Person.key :foo, Module.new }
42
+ assert_raise(TypeError) { Person.key :foo, String, :default => 1 }
43
+ end
44
+
45
+ test "should store defaults" do
46
+ assert_equal [], Person.new.friends
47
+ end
48
+
49
+ test "should typecheck" do
50
+ person = Person.new
51
+ person.age = '18'
52
+ assert_equal 18, person.age
53
+
54
+ person.age = nil
55
+ assert_nil person.age
56
+ end
57
+
58
+ test "should handle arrays" do
59
+ person = Person.new
60
+ assert_equal [], person.friends
61
+
62
+ person.friends << Person.new
63
+ assert_equal 1, person.friends.size
64
+ assert_equal 0, person.friends.first.friends.size
65
+ end
66
+
67
+ test "should translate to hash" do
68
+ person = Person.new(:name => 'John')
69
+ person.friends << Person.new(:name => 'Jane')
70
+ assert_equal 'John', person.to_hash[:name]
71
+ assert_equal 'Jane', person.to_hash[:friends].first[:name]
72
+ end
73
+
74
+ test "should translate to JSON" do
75
+ person = Person.new
76
+ person.friends << Person.new
77
+ json = person.to_json
78
+ assert_kind_of Person, JSON.parse(json)
79
+ assert_kind_of Person, JSON.parse(json).friends.first
80
+ end
81
+
82
+ test "should translate to JSON in a Rails app" do
83
+ person = Person.new
84
+ assert_equal false, person.respond_to?(:as_json)
85
+
86
+ require 'active_support/ordered_hash'
87
+ require 'active_support/json'
88
+ require 'structure/rails'
89
+ assert_equal true, person.as_json(:only => :name).has_key?(:name)
90
+ assert_equal false, person.as_json(:except => :name).has_key?(:name)
91
+ end
92
+ end
93
+
94
+ class City < Structure
95
+ key :name
96
+ end
97
+
98
+ class Stadt < Structure
99
+ key :name
100
+ end
101
+
102
+ class TestStatic < Test::Unit::TestCase
103
+ def fix(klass, path)
104
+ klass.instance_variable_set(:@all, nil)
105
+ klass.instance_variable_set(:@id_cnt, nil)
106
+ fixture = File.expand_path("../fixtures/#{path}.yml", __FILE__)
107
+ klass.set_data_file(fixture)
108
+ end
109
+
110
+ test "should enumerate at the class level" do
111
+ fix City, 'cities'
112
+ assert_respond_to City, :map
113
+ end
114
+
115
+ test "should return all records" do
116
+ fix City, 'cities'
117
+ cities = City.all
118
+ assert_kind_of City, cities.first
119
+ assert_equal 2, cities.size
120
+ end
121
+
122
+ test "should find a record" do
123
+ fix City, 'cities'
124
+ assert 'New York', City.find(1).name
125
+ assert_nil City.find(4)
126
+ end
127
+
128
+ test "should work if records contain no id field" do
129
+ fix City, 'cities_without_ids'
130
+ assert_equal 'New York', City.find(1).name
131
+ assert_equal 'Paris', City.find(3).name
132
+ end
133
+
134
+ test "should auto increment independently in each structure" do
135
+ fix City, 'cities_without_ids'
136
+ fix Stadt, 'cities_without_ids'
137
+ assert_equal 'New York', City.find(1).name
138
+ assert_equal 'New York', Stadt.find(1).name
139
+ end
140
+ 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.16.0
4
+ version: 0.17.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,42 +9,9 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
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
12
+ date: 2011-08-26 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Structure is a typed, nestable key/value container.
48
15
  email:
49
16
  - code@papercavalier.com
50
17
  executables: []
@@ -58,18 +25,13 @@ files:
58
25
  - README.md
59
26
  - Rakefile
60
27
  - lib/structure.rb
61
- - lib/structure/collection.rb
62
- - lib/structure/document.rb
63
- - lib/structure/document/static.rb
28
+ - lib/structure/rails.rb
29
+ - lib/structure/static.rb
64
30
  - lib/structure/version.rb
65
31
  - structure.gemspec
66
- - test/collection_test.rb
67
- - test/document_test.rb
68
32
  - test/fixtures/cities.yml
69
- - test/fixtures/cities_with_neighborhoods.yml
70
33
  - test/fixtures/cities_without_ids.yml
71
- - test/helper.rb
72
- - test/static_test.rb
34
+ - test/structure_test.rb
73
35
  homepage: http://github.com/hakanensari/structure
74
36
  licenses: []
75
37
  post_install_message:
@@ -84,7 +46,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
84
46
  version: '0'
85
47
  segments:
86
48
  - 0
87
- hash: -2067006556322233036
49
+ hash: -4025252562895677644
88
50
  required_rubygems_version: !ruby/object:Gem::Requirement
89
51
  none: false
90
52
  requirements:
@@ -93,7 +55,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
93
55
  version: '0'
94
56
  segments:
95
57
  - 0
96
- hash: -2067006556322233036
58
+ hash: -4025252562895677644
97
59
  requirements: []
98
60
  rubyforge_project: structure
99
61
  rubygems_version: 1.8.6
@@ -101,10 +63,6 @@ signing_key:
101
63
  specification_version: 3
102
64
  summary: A typed, nestable key/value container
103
65
  test_files:
104
- - test/collection_test.rb
105
- - test/document_test.rb
106
66
  - test/fixtures/cities.yml
107
- - test/fixtures/cities_with_neighborhoods.yml
108
67
  - test/fixtures/cities_without_ids.yml
109
- - test/helper.rb
110
- - test/static_test.rb
68
+ - test/structure_test.rb
@@ -1,67 +0,0 @@
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
@@ -1,66 +0,0 @@
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
@@ -1,212 +0,0 @@
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,33 +0,0 @@
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
@@ -1,113 +0,0 @@
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
@@ -1,7 +0,0 @@
1
- ---
2
- - id: 1
3
- name: New York
4
- neighborhoods:
5
- - name: Manhattan
6
- - name: Brooklyn
7
- - name: Queens
data/test/helper.rb DELETED
@@ -1,13 +0,0 @@
1
- $:.push File.expand_path('../../lib', __FILE__)
2
-
3
- require 'bundler/setup'
4
-
5
- begin
6
- require 'ruby-debug'
7
- rescue LoadError
8
- end
9
-
10
- require 'structure'
11
- require 'test/unit'
12
-
13
- Document = Structure::Document
data/test/static_test.rb DELETED
@@ -1,62 +0,0 @@
1
- require File.expand_path('../helper.rb', __FILE__)
2
-
3
- class City < Document
4
- include Static
5
-
6
- key :name
7
- many :neighborhoods
8
- end
9
-
10
- class Neighborhood < Document
11
- key :name
12
- end
13
-
14
- class Dummy < Document
15
- include Static
16
-
17
- key :name
18
- end
19
-
20
- class TestStatic < Test::Unit::TestCase
21
- def fixture(klass, path)
22
- klass.instance_variable_set(:@records, nil)
23
- klass.instance_variable_set(:@increment_id, nil)
24
- fixture = File.expand_path("../fixtures/#{path}.yml", __FILE__)
25
- klass.set_data_path(fixture)
26
- end
27
-
28
- def test_class_enumeration
29
- assert_respond_to City, :map
30
- end
31
-
32
- def test_all
33
- fixture City, 'cities'
34
- cities = City.all
35
- assert_kind_of City, cities.first
36
- assert_equal 2, cities.size
37
- end
38
-
39
- def test_find
40
- fixture City, 'cities'
41
- assert 'New York', City.find(1).name
42
- assert_nil City.find(4)
43
- end
44
-
45
- def test_data_without_ids
46
- fixture City, 'cities_without_ids'
47
- assert_equal 'New York', City.find(1).name
48
- assert_equal 'Paris', City.find(3).name
49
- end
50
-
51
- def test_auto_increment
52
- fixture City, 'cities_without_ids'
53
- fixture Dummy, 'cities_without_ids'
54
- assert_equal 'New York', City.find(1).name
55
- assert_equal 'New York', Dummy.find(1).name
56
- end
57
-
58
- def test_nesting
59
- fixture City, 'cities_with_neighborhoods'
60
- assert_kind_of Neighborhood, City.first.neighborhoods.first
61
- end
62
- end