structure 0.16.0 → 0.17.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/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