toystore 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.travis.yml +9 -0
- data/Changelog.md +10 -1
- data/Gemfile +14 -13
- data/README.md +213 -2
- data/examples/plain_old_object.rb +54 -0
- data/examples/plain_old_object_on_roids.rb +160 -0
- data/lib/toy.rb +3 -1
- data/lib/toy/association_serialization.rb +50 -0
- data/lib/toy/attribute.rb +17 -2
- data/lib/toy/equality.rb +5 -1
- data/lib/toy/identity/abstract_key_factory.rb +5 -0
- data/lib/toy/identity_map.rb +1 -2
- data/lib/toy/inheritance.rb +29 -0
- data/lib/toy/inspect.rb +17 -4
- data/lib/toy/object.rb +6 -0
- data/lib/toy/querying.rb +20 -3
- data/lib/toy/reference.rb +18 -4
- data/lib/toy/reloadable.rb +2 -2
- data/lib/toy/serialization.rb +1 -40
- data/lib/toy/store.rb +2 -2
- data/lib/toy/timestamps.rb +3 -1
- data/lib/toy/version.rb +2 -2
- data/spec/helper.rb +2 -3
- data/spec/support/constants.rb +15 -15
- data/spec/toy/association_serialization_spec.rb +103 -0
- data/spec/toy/attribute_spec.rb +17 -1
- data/spec/toy/equality_spec.rb +9 -2
- data/spec/toy/extensions/array_spec.rb +2 -2
- data/spec/toy/identity/uuid_key_factory_spec.rb +35 -3
- data/spec/toy/identity_map_spec.rb +4 -0
- data/spec/toy/inheritance_spec.rb +93 -0
- data/spec/toy/inspect_spec.rb +12 -4
- data/spec/toy/object_spec.rb +47 -0
- data/spec/toy/plugins_spec.rb +4 -4
- data/spec/toy/querying_spec.rb +71 -11
- data/spec/toy/reference_spec.rb +82 -72
- data/spec/toy/serialization_spec.rb +16 -111
- data/spec/toy/store_spec.rb +14 -28
- metadata +23 -13
- data/Gemfile.lock +0 -71
data/lib/toy.rb
CHANGED
@@ -72,12 +72,14 @@ module Toy
|
|
72
72
|
autoload 'Cloneable', 'toy/cloneable'
|
73
73
|
autoload 'Equality', 'toy/equality'
|
74
74
|
autoload 'Inspect', 'toy/inspect'
|
75
|
+
autoload 'Inheritance', 'toy/inheritance'
|
75
76
|
autoload 'Logger', 'toy/logger'
|
76
77
|
autoload 'MassAssignmentSecurity', 'toy/mass_assignment_security'
|
77
78
|
autoload 'Persistence', 'toy/persistence'
|
78
79
|
autoload 'Querying', 'toy/querying'
|
79
80
|
autoload 'Reloadable', 'toy/reloadable'
|
80
81
|
autoload 'Serialization', 'toy/serialization'
|
82
|
+
autoload 'AssociationSerialization','toy/association_serialization'
|
81
83
|
autoload 'Timestamps', 'toy/timestamps'
|
82
84
|
autoload 'Validations', 'toy/validations'
|
83
85
|
|
@@ -100,4 +102,4 @@ require 'toy/plugins'
|
|
100
102
|
require 'toy/object'
|
101
103
|
require 'toy/store'
|
102
104
|
|
103
|
-
Toy::IdentityMap.enabled = false
|
105
|
+
Toy::IdentityMap.enabled = false
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Toy
|
2
|
+
module AssociationSerialization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Serialization
|
5
|
+
|
6
|
+
def serializable_hash(options = nil)
|
7
|
+
options ||= {}
|
8
|
+
super.tap { |hash|
|
9
|
+
serializable_add_includes(options) do |association, records, opts|
|
10
|
+
hash[association] = records.is_a?(Enumerable) ?
|
11
|
+
records.map { |r| r.serializable_hash(opts) } :
|
12
|
+
records.serializable_hash(opts)
|
13
|
+
end
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
# Add associations specified via the <tt>:includes</tt> option.
|
20
|
+
# Expects a block that takes as arguments:
|
21
|
+
# +association+ - name of the association
|
22
|
+
# +records+ - the association record(s) to be serialized
|
23
|
+
# +opts+ - options for the association records
|
24
|
+
def serializable_add_includes(options = {})
|
25
|
+
return unless include_associations = options.delete(:include)
|
26
|
+
|
27
|
+
base_only_or_except = { :except => options[:except],
|
28
|
+
:only => options[:only] }
|
29
|
+
|
30
|
+
include_has_options = include_associations.is_a?(Hash)
|
31
|
+
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
|
32
|
+
|
33
|
+
for association in associations
|
34
|
+
records = if self.class.list?(association)
|
35
|
+
send(association).to_a
|
36
|
+
elsif self.class.reference?(association) || self.class.parent_reference?(association)
|
37
|
+
send(association)
|
38
|
+
end
|
39
|
+
|
40
|
+
unless records.nil?
|
41
|
+
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
42
|
+
opts = options.merge(association_options)
|
43
|
+
yield(association, records, opts)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
options[:include] = include_associations
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/toy/attribute.rb
CHANGED
@@ -28,7 +28,12 @@ module Toy
|
|
28
28
|
|
29
29
|
def default
|
30
30
|
if options.key?(:default)
|
31
|
-
|
31
|
+
default = options[:default]
|
32
|
+
if default.respond_to?(:call)
|
33
|
+
backwards_compatible_call(default)
|
34
|
+
else
|
35
|
+
default
|
36
|
+
end
|
32
37
|
else
|
33
38
|
type.respond_to?(:store_default) ? type.store_default : nil
|
34
39
|
end
|
@@ -64,5 +69,15 @@ module Toy
|
|
64
69
|
name == other.name
|
65
70
|
end
|
66
71
|
alias :== :eql?
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def backwards_compatible_call(block)
|
76
|
+
if block.arity == 1
|
77
|
+
block.call(model)
|
78
|
+
else
|
79
|
+
block.call
|
80
|
+
end
|
81
|
+
end
|
67
82
|
end
|
68
|
-
end
|
83
|
+
end
|
data/lib/toy/equality.rb
CHANGED
data/lib/toy/identity_map.rb
CHANGED
@@ -52,10 +52,9 @@ module Toy
|
|
52
52
|
def get_from_identity_map(id)
|
53
53
|
IdentityMap.repository[id] if IdentityMap.enabled?
|
54
54
|
end
|
55
|
+
private :get_from_identity_map
|
55
56
|
|
56
57
|
def load(id, attrs)
|
57
|
-
return nil if attrs.nil?
|
58
|
-
|
59
58
|
if IdentityMap.enabled? && instance = IdentityMap.repository[id]
|
60
59
|
instance
|
61
60
|
else
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Toy
|
2
|
+
module Inheritance
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
DuplicatedInstanceVariables = [
|
7
|
+
:attributes,
|
8
|
+
:key_factory,
|
9
|
+
:lists,
|
10
|
+
:references,
|
11
|
+
:adapter,
|
12
|
+
]
|
13
|
+
|
14
|
+
def inherited(subclass)
|
15
|
+
DuplicatedInstanceVariables.each do |name|
|
16
|
+
subclass.instance_variable_set("@#{name}", send(name).dup) if respond_to?(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
subclass.attribute(:type, String, :default => subclass.name)
|
20
|
+
|
21
|
+
super
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def type
|
26
|
+
read_attribute(:type)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/toy/inspect.rb
CHANGED
@@ -2,11 +2,24 @@ module Toy
|
|
2
2
|
module Inspect
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
|
+
module ClassMethods
|
6
|
+
def inspect
|
7
|
+
keys = attributes.keys - ['id']
|
8
|
+
nice_string = keys.sort.map do |name|
|
9
|
+
type = attributes[name].type
|
10
|
+
"#{name}:#{type}"
|
11
|
+
end.join(" ")
|
12
|
+
"#{name}(id:#{attributes['id'].type} #{nice_string})"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
5
16
|
def inspect
|
6
|
-
|
17
|
+
keys = self.class.attributes.keys - ['id']
|
18
|
+
attributes_as_nice_string = keys.map(&:to_s).sort.map do |name|
|
7
19
|
"#{name}: #{read_attribute(name).inspect}"
|
8
|
-
end
|
9
|
-
"
|
20
|
+
end
|
21
|
+
attributes_as_nice_string.unshift("id: #{read_attribute(:id).inspect}")
|
22
|
+
"#<#{self.class}:#{object_id} #{attributes_as_nice_string.join(', ')}>"
|
10
23
|
end
|
11
24
|
end
|
12
|
-
end
|
25
|
+
end
|
data/lib/toy/object.rb
CHANGED
data/lib/toy/querying.rb
CHANGED
@@ -4,7 +4,9 @@ module Toy
|
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
def get(id)
|
7
|
-
|
7
|
+
if (attrs = adapter.read(id))
|
8
|
+
load(id, attrs)
|
9
|
+
end
|
8
10
|
end
|
9
11
|
|
10
12
|
def get!(id)
|
@@ -29,8 +31,23 @@ module Toy
|
|
29
31
|
alias :has_key? :key?
|
30
32
|
|
31
33
|
def load(id, attrs)
|
32
|
-
attrs
|
34
|
+
attrs ||= {}
|
35
|
+
instance = constant_from_attrs(attrs).allocate
|
36
|
+
instance.initialize_from_database(attrs.update('id' => id))
|
33
37
|
end
|
38
|
+
|
39
|
+
def constant_from_attrs(attrs)
|
40
|
+
return self if attrs.nil?
|
41
|
+
|
42
|
+
type = attrs[:type] || attrs['type']
|
43
|
+
|
44
|
+
return self if type.nil?
|
45
|
+
|
46
|
+
type.constantize
|
47
|
+
rescue NameError
|
48
|
+
self
|
49
|
+
end
|
50
|
+
private :constant_from_attrs
|
34
51
|
end
|
35
52
|
end
|
36
|
-
end
|
53
|
+
end
|
data/lib/toy/reference.rb
CHANGED
@@ -120,17 +120,31 @@ module Toy
|
|
120
120
|
def create_accessors
|
121
121
|
model.class_eval """
|
122
122
|
def #{name}
|
123
|
-
#{instance_variable} ||= self.class.references[:#{name}].new_proxy(self)
|
123
|
+
#{instance_variable} ||= self.class.references[:#{name}].new_proxy(self).target
|
124
124
|
end
|
125
125
|
|
126
126
|
def #{name}=(record)
|
127
|
-
|
127
|
+
self.class.references[:#{name}].new_proxy(self).replace(record)
|
128
|
+
#{instance_variable} = record
|
128
129
|
end
|
129
130
|
|
130
131
|
def #{name}?
|
131
|
-
|
132
|
+
!!#{name}
|
133
|
+
end
|
134
|
+
|
135
|
+
def build_#{name}(attrs={})
|
136
|
+
self.class.references[:#{name}].new_proxy(self).build(attrs)
|
137
|
+
end
|
138
|
+
|
139
|
+
def create_#{name}(attrs={})
|
140
|
+
self.class.references[:#{name}].new_proxy(self).create(attrs)
|
141
|
+
end
|
142
|
+
|
143
|
+
def reset_#{name}
|
144
|
+
#{instance_variable} = nil
|
145
|
+
self.class.references[:#{name}].new_proxy(self).reset
|
132
146
|
end
|
133
147
|
"""
|
134
148
|
end
|
135
149
|
end
|
136
|
-
end
|
150
|
+
end
|
data/lib/toy/reloadable.rb
CHANGED
@@ -7,11 +7,11 @@ module Toy
|
|
7
7
|
initialize_attributes_with_defaults
|
8
8
|
send(:attributes=, attrs, new_record?)
|
9
9
|
self.class.lists.each_key { |name| send(name).reset }
|
10
|
-
self.class.references.each_key { |name| send(name)
|
10
|
+
self.class.references.each_key { |name| send("reset_#{name}") }
|
11
11
|
else
|
12
12
|
raise NotFound.new(id)
|
13
13
|
end
|
14
14
|
self
|
15
15
|
end
|
16
16
|
end
|
17
|
-
end
|
17
|
+
end
|
data/lib/toy/serialization.rb
CHANGED
@@ -37,46 +37,7 @@ module Toy
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
-
serializable_add_includes(options) do |association, records, opts|
|
41
|
-
hash[association] = records.is_a?(Enumerable) ?
|
42
|
-
records.map { |r| r.serializable_hash(opts) } :
|
43
|
-
records.serializable_hash(opts)
|
44
|
-
end
|
45
|
-
|
46
40
|
hash
|
47
41
|
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
# Add associations specified via the <tt>:includes</tt> option.
|
52
|
-
# Expects a block that takes as arguments:
|
53
|
-
# +association+ - name of the association
|
54
|
-
# +records+ - the association record(s) to be serialized
|
55
|
-
# +opts+ - options for the association records
|
56
|
-
def serializable_add_includes(options = {})
|
57
|
-
return unless include_associations = options.delete(:include)
|
58
|
-
|
59
|
-
base_only_or_except = { :except => options[:except],
|
60
|
-
:only => options[:only] }
|
61
|
-
|
62
|
-
include_has_options = include_associations.is_a?(Hash)
|
63
|
-
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
|
64
|
-
|
65
|
-
for association in associations
|
66
|
-
records = if self.class.list?(association)
|
67
|
-
send(association).to_a
|
68
|
-
elsif self.class.reference?(association) || self.class.parent_reference?(association)
|
69
|
-
send(association)
|
70
|
-
end
|
71
|
-
|
72
|
-
unless records.nil?
|
73
|
-
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
74
|
-
opts = options.merge(association_options)
|
75
|
-
yield(association, records, opts)
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
options[:include] = include_associations
|
80
|
-
end
|
81
42
|
end
|
82
|
-
end
|
43
|
+
end
|
data/lib/toy/store.rb
CHANGED
@@ -13,14 +13,14 @@ module Toy
|
|
13
13
|
|
14
14
|
include Callbacks
|
15
15
|
include Validations
|
16
|
-
include Serialization
|
17
16
|
include Timestamps
|
18
17
|
|
19
18
|
include Lists
|
20
19
|
include References
|
20
|
+
include AssociationSerialization
|
21
21
|
|
22
22
|
include IdentityMap
|
23
23
|
include Caching
|
24
24
|
end
|
25
25
|
end
|
26
|
-
end
|
26
|
+
end
|
data/lib/toy/timestamps.rb
CHANGED
data/lib/toy/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
module Toy
|
2
|
-
VERSION = "0.
|
3
|
-
end
|
2
|
+
VERSION = "0.10.0"
|
3
|
+
end
|
data/spec/helper.rb
CHANGED
@@ -11,7 +11,7 @@ log_path.mkpath
|
|
11
11
|
require 'rubygems'
|
12
12
|
require 'bundler'
|
13
13
|
|
14
|
-
Bundler.require(:default, :
|
14
|
+
Bundler.require(:default, :test)
|
15
15
|
|
16
16
|
require 'toy'
|
17
17
|
require 'support/constants'
|
@@ -20,7 +20,6 @@ require 'support/identity_map_matcher'
|
|
20
20
|
require 'support/name_and_number_key_factory'
|
21
21
|
|
22
22
|
Logger.new(log_path.join('test.log')).tap do |log|
|
23
|
-
LogBuddy.init(:logger => log)
|
24
23
|
Toy.logger = log
|
25
24
|
end
|
26
25
|
|
@@ -35,4 +34,4 @@ RSpec.configure do |c|
|
|
35
34
|
Toy.reset
|
36
35
|
Toy.key_factory = nil
|
37
36
|
end
|
38
|
-
end
|
37
|
+
end
|
data/spec/support/constants.rb
CHANGED
@@ -4,35 +4,35 @@ module Support
|
|
4
4
|
|
5
5
|
module ClassMethods
|
6
6
|
def uses_constants(*constants)
|
7
|
-
before { create_constants
|
7
|
+
before { create_constants *constants }
|
8
|
+
after { remove_constants *constants }
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
11
12
|
def create_constants(*constants)
|
12
|
-
constants.each { |constant| create_constant
|
13
|
+
constants.each { |constant| create_constant constant }
|
13
14
|
end
|
14
15
|
|
15
16
|
def remove_constants(*constants)
|
16
|
-
constants.each { |constant| remove_constant
|
17
|
+
constants.each { |constant| remove_constant constant }
|
17
18
|
end
|
18
19
|
|
19
|
-
def create_constant(constant)
|
20
|
-
|
21
|
-
Kernel.const_set(constant, Model(constant))
|
20
|
+
def create_constant(constant, superclass=nil)
|
21
|
+
Object.const_set constant, Model(superclass)
|
22
22
|
end
|
23
23
|
|
24
24
|
def remove_constant(constant)
|
25
|
-
|
25
|
+
if Object.const_defined?(constant)
|
26
|
+
Object.send :remove_const, constant
|
27
|
+
end
|
26
28
|
end
|
27
29
|
|
28
|
-
def Model(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
""" if name
|
34
|
-
model.send(:include, Toy::Store)
|
30
|
+
def Model(superclass=nil)
|
31
|
+
if superclass.nil?
|
32
|
+
Class.new { include Toy::Store }
|
33
|
+
else
|
34
|
+
Class.new(superclass) { include Toy::Store }
|
35
35
|
end
|
36
36
|
end
|
37
37
|
end
|
38
|
-
end
|
38
|
+
end
|