toystore 0.9.0 → 0.10.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/.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
|