toystore 0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.autotest +11 -0
- data/.bundle/config +2 -0
- data/.gitignore +6 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +49 -0
- data/LICENSE +9 -0
- data/LOGGING.rdoc +16 -0
- data/README.rdoc +13 -0
- data/Rakefile +7 -0
- data/examples/memcached.rb +20 -0
- data/examples/memory.rb +20 -0
- data/examples/models.rb +51 -0
- data/examples/redis.rb +20 -0
- data/lib/toy.rb +81 -0
- data/lib/toy/attribute.rb +73 -0
- data/lib/toy/attributes.rb +137 -0
- data/lib/toy/caching.rb +20 -0
- data/lib/toy/callbacks.rb +48 -0
- data/lib/toy/collection.rb +55 -0
- data/lib/toy/connection.rb +28 -0
- data/lib/toy/dirty.rb +47 -0
- data/lib/toy/dolly.rb +30 -0
- data/lib/toy/embedded_list.rb +45 -0
- data/lib/toy/embedded_lists.rb +68 -0
- data/lib/toy/equality.rb +19 -0
- data/lib/toy/exceptions.rb +29 -0
- data/lib/toy/extensions/array.rb +22 -0
- data/lib/toy/extensions/boolean.rb +43 -0
- data/lib/toy/extensions/date.rb +24 -0
- data/lib/toy/extensions/float.rb +13 -0
- data/lib/toy/extensions/hash.rb +17 -0
- data/lib/toy/extensions/integer.rb +22 -0
- data/lib/toy/extensions/nil_class.rb +17 -0
- data/lib/toy/extensions/object.rb +26 -0
- data/lib/toy/extensions/set.rb +23 -0
- data/lib/toy/extensions/string.rb +17 -0
- data/lib/toy/extensions/time.rb +29 -0
- data/lib/toy/identity.rb +26 -0
- data/lib/toy/identity/abstract_key_factory.rb +10 -0
- data/lib/toy/identity/uuid_key_factory.rb +9 -0
- data/lib/toy/identity_map.rb +109 -0
- data/lib/toy/index.rb +74 -0
- data/lib/toy/indices.rb +56 -0
- data/lib/toy/inspect.rb +12 -0
- data/lib/toy/list.rb +46 -0
- data/lib/toy/lists.rb +37 -0
- data/lib/toy/logger.rb +26 -0
- data/lib/toy/mass_assignment_security.rb +16 -0
- data/lib/toy/persistence.rb +138 -0
- data/lib/toy/plugins.rb +23 -0
- data/lib/toy/proxies/embedded_list.rb +74 -0
- data/lib/toy/proxies/list.rb +97 -0
- data/lib/toy/proxies/proxy.rb +59 -0
- data/lib/toy/querying.rb +57 -0
- data/lib/toy/reference.rb +134 -0
- data/lib/toy/references.rb +19 -0
- data/lib/toy/serialization.rb +81 -0
- data/lib/toy/store.rb +36 -0
- data/lib/toy/timestamps.rb +22 -0
- data/lib/toy/validations.rb +45 -0
- data/lib/toy/version.rb +3 -0
- data/lib/toystore.rb +1 -0
- data/spec/helper.rb +35 -0
- data/spec/spec.opts +3 -0
- data/spec/support/constants.rb +41 -0
- data/spec/support/identity_map_matcher.rb +20 -0
- data/spec/support/name_and_number_key_factory.rb +5 -0
- data/spec/toy/attribute_spec.rb +176 -0
- data/spec/toy/attributes_spec.rb +394 -0
- data/spec/toy/caching_spec.rb +62 -0
- data/spec/toy/callbacks_spec.rb +97 -0
- data/spec/toy/connection_spec.rb +47 -0
- data/spec/toy/dirty_spec.rb +99 -0
- data/spec/toy/dolly_spec.rb +76 -0
- data/spec/toy/embedded_list_spec.rb +607 -0
- data/spec/toy/embedded_lists_spec.rb +172 -0
- data/spec/toy/equality_spec.rb +46 -0
- data/spec/toy/exceptions_spec.rb +18 -0
- data/spec/toy/extensions/array_spec.rb +25 -0
- data/spec/toy/extensions/boolean_spec.rb +41 -0
- data/spec/toy/extensions/date_spec.rb +48 -0
- data/spec/toy/extensions/float_spec.rb +14 -0
- data/spec/toy/extensions/hash_spec.rb +21 -0
- data/spec/toy/extensions/integer_spec.rb +29 -0
- data/spec/toy/extensions/nil_class_spec.rb +14 -0
- data/spec/toy/extensions/set_spec.rb +27 -0
- data/spec/toy/extensions/string_spec.rb +28 -0
- data/spec/toy/extensions/time_spec.rb +94 -0
- data/spec/toy/identity/abstract_key_factory_spec.rb +7 -0
- data/spec/toy/identity/uuid_key_factory_spec.rb +7 -0
- data/spec/toy/identity_map_spec.rb +150 -0
- data/spec/toy/identity_spec.rb +52 -0
- data/spec/toy/index_spec.rb +230 -0
- data/spec/toy/indices_spec.rb +141 -0
- data/spec/toy/inspect_spec.rb +15 -0
- data/spec/toy/list_spec.rb +576 -0
- data/spec/toy/lists_spec.rb +95 -0
- data/spec/toy/logger_spec.rb +33 -0
- data/spec/toy/mass_assignment_security_spec.rb +116 -0
- data/spec/toy/persistence_spec.rb +312 -0
- data/spec/toy/plugins_spec.rb +39 -0
- data/spec/toy/querying_spec.rb +162 -0
- data/spec/toy/reference_spec.rb +400 -0
- data/spec/toy/references_spec.rb +86 -0
- data/spec/toy/serialization_spec.rb +354 -0
- data/spec/toy/store_spec.rb +41 -0
- data/spec/toy/timestamps_spec.rb +63 -0
- data/spec/toy/validations_spec.rb +171 -0
- data/spec/toy_spec.rb +26 -0
- data/specs.watchr +52 -0
- data/test/lint_test.rb +40 -0
- data/toystore.gemspec +24 -0
- metadata +290 -0
data/lib/toy/inspect.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Toy
|
2
|
+
module Inspect
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def inspect
|
6
|
+
attributes_as_nice_string = self.class.attributes.keys.map(&:to_s).sort.map do |name|
|
7
|
+
"#{name}: #{read_attribute(name).inspect}"
|
8
|
+
end.join(", ")
|
9
|
+
"#<#{self.class}:#{object_id} #{attributes_as_nice_string}>"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/lib/toy/list.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'toy/proxies/list'
|
2
|
+
|
3
|
+
module Toy
|
4
|
+
class List
|
5
|
+
include Toy::Collection
|
6
|
+
|
7
|
+
def after_initialize
|
8
|
+
model.attribute(key, Array)
|
9
|
+
create_accessors
|
10
|
+
end
|
11
|
+
|
12
|
+
def key
|
13
|
+
@key ||= :"#{name.to_s.singularize}_ids"
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
def proxy_class
|
18
|
+
Toy::Proxies::List
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_method
|
22
|
+
:lists
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_accessors
|
26
|
+
model.class_eval """
|
27
|
+
def #{name}
|
28
|
+
#{instance_variable} ||= self.class.#{list_method}[:#{name}].new_proxy(self)
|
29
|
+
end
|
30
|
+
|
31
|
+
def #{name}=(records)
|
32
|
+
#{name}.replace(records)
|
33
|
+
end
|
34
|
+
"""
|
35
|
+
|
36
|
+
if options[:dependent]
|
37
|
+
model.class_eval """
|
38
|
+
after_destroy :destroy_#{name}
|
39
|
+
def destroy_#{name}
|
40
|
+
#{name}.each { |o| o.destroy }
|
41
|
+
end
|
42
|
+
"""
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
data/lib/toy/lists.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Toy
|
2
|
+
module Lists
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def lists
|
7
|
+
@lists ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def list?(key)
|
11
|
+
lists.keys.include?(key.to_sym)
|
12
|
+
end
|
13
|
+
|
14
|
+
# @examples
|
15
|
+
# list :games # assumes Game
|
16
|
+
# list :games, :dependent => true # assumes Game
|
17
|
+
# list :active_games, Game # uses Game
|
18
|
+
# list :active_games, Game, :dependent => true # uses Game
|
19
|
+
#
|
20
|
+
# list :games do
|
21
|
+
# def active
|
22
|
+
# target.select { |t| t.active? }
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# module ActiveExtension
|
27
|
+
# def active
|
28
|
+
# target.select { |t| t.active? }
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# list :games, :extensions => [ActiveExtension]
|
32
|
+
def list(name, *args, &block)
|
33
|
+
List.new(self, name, *args, &block)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/toy/logger.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Toy
|
2
|
+
module Logger
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def logger
|
7
|
+
Toy.logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def log_operation(operation, adapter, key, value)
|
11
|
+
logger.debug("ToyStore #{operation} :#{adapter.name} #{key.inspect}")
|
12
|
+
logger.debug(" #{value.inspect}")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
def logger
|
18
|
+
Toy.logger
|
19
|
+
end
|
20
|
+
|
21
|
+
def log_operation(*args)
|
22
|
+
self.class.log_operation(*args)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Toy
|
2
|
+
module MassAssignmentSecurity
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::MassAssignmentSecurity
|
5
|
+
|
6
|
+
module InstanceMethods
|
7
|
+
def initialize(attrs={})
|
8
|
+
super(sanitize_for_mass_assignment(attrs || {}))
|
9
|
+
end
|
10
|
+
|
11
|
+
def update_attributes(attrs={})
|
12
|
+
super(sanitize_for_mass_assignment(attrs || {}))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Toy
|
2
|
+
module Persistence
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def store(name=nil, client=nil, options=nil)
|
7
|
+
assert_client(name, client)
|
8
|
+
@store = Adapter[name].new(client, options) if !name.nil? && !client.nil?
|
9
|
+
assert_store(name, client, 'store')
|
10
|
+
@store
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_store?
|
14
|
+
!@store.nil?
|
15
|
+
end
|
16
|
+
|
17
|
+
def cache(name=nil, client=nil)
|
18
|
+
assert_client(name, client)
|
19
|
+
@cache = Adapter[name].new(client) if !name.nil? && !client.nil?
|
20
|
+
assert_store(name, client, 'cache')
|
21
|
+
@cache
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_cache?
|
25
|
+
!@cache.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
def namespace(new_namespace=nil)
|
29
|
+
if new_namespace.nil?
|
30
|
+
@namespace ||= self.name
|
31
|
+
else
|
32
|
+
@namespace = new_namespace
|
33
|
+
end
|
34
|
+
|
35
|
+
@namespace
|
36
|
+
end
|
37
|
+
|
38
|
+
def store_key(id)
|
39
|
+
[namespace, id].join(':')
|
40
|
+
end
|
41
|
+
|
42
|
+
def create(attrs={})
|
43
|
+
new(attrs).tap { |doc| doc.save }
|
44
|
+
end
|
45
|
+
|
46
|
+
def delete(*ids)
|
47
|
+
ids.each { |id| get(id).try(:delete) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def destroy(*ids)
|
51
|
+
ids.each { |id| get(id).try(:destroy) }
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
def assert_client(name, client)
|
56
|
+
raise(ArgumentError, 'Client is required') if !name.nil? && client.nil?
|
57
|
+
end
|
58
|
+
|
59
|
+
def assert_store(name, client, which)
|
60
|
+
raise(StandardError, "No #{which} has been set") if name.nil? && client.nil? && !send(:"has_#{which}?")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
module InstanceMethods
|
65
|
+
def store
|
66
|
+
self.class.store
|
67
|
+
end
|
68
|
+
|
69
|
+
def cache
|
70
|
+
self.class.cache
|
71
|
+
end
|
72
|
+
|
73
|
+
def store_key
|
74
|
+
self.class.store_key(id)
|
75
|
+
end
|
76
|
+
|
77
|
+
def new_record?
|
78
|
+
@_new_record == true
|
79
|
+
end
|
80
|
+
|
81
|
+
def destroyed?
|
82
|
+
@_destroyed == true
|
83
|
+
end
|
84
|
+
|
85
|
+
def persisted?
|
86
|
+
!new_record? && !destroyed?
|
87
|
+
end
|
88
|
+
|
89
|
+
def save(*)
|
90
|
+
new_record? ? create : update
|
91
|
+
end
|
92
|
+
|
93
|
+
def update_attributes(attrs)
|
94
|
+
self.attributes = attrs
|
95
|
+
save
|
96
|
+
end
|
97
|
+
|
98
|
+
def destroy
|
99
|
+
delete
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete
|
103
|
+
key = store_key
|
104
|
+
@_destroyed = true
|
105
|
+
logger.debug("ToyStore DEL #{key.inspect}")
|
106
|
+
store.delete(key)
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
def create
|
111
|
+
persist!
|
112
|
+
end
|
113
|
+
|
114
|
+
def update
|
115
|
+
persist!
|
116
|
+
end
|
117
|
+
|
118
|
+
def persist
|
119
|
+
@_new_record = false
|
120
|
+
end
|
121
|
+
|
122
|
+
def persist!
|
123
|
+
key, attrs = store_key, persisted_attributes
|
124
|
+
|
125
|
+
if self.class.has_cache?
|
126
|
+
cache.write(key, attrs)
|
127
|
+
log_operation('WTS', cache, key, attrs)
|
128
|
+
end
|
129
|
+
|
130
|
+
store.write(key, attrs)
|
131
|
+
log_operation('SET', store, key, attrs)
|
132
|
+
persist
|
133
|
+
each_embedded_object(&:persist)
|
134
|
+
true
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/toy/plugins.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Toy
|
2
|
+
def models
|
3
|
+
@models ||= Set.new
|
4
|
+
end
|
5
|
+
|
6
|
+
def plugins
|
7
|
+
@plugins ||= Set.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def plugin(mod)
|
11
|
+
Toy.models.each { |model| model.send(:include, mod) }
|
12
|
+
plugins << mod
|
13
|
+
end
|
14
|
+
|
15
|
+
module Plugins
|
16
|
+
extend ActiveSupport::Concern
|
17
|
+
|
18
|
+
included do
|
19
|
+
Toy.models << self
|
20
|
+
Toy.plugins.each { |mod| include mod }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'toy/proxies/proxy'
|
2
|
+
|
3
|
+
module Toy
|
4
|
+
module Proxies
|
5
|
+
class EmbeddedList < Toy::Proxies::Proxy
|
6
|
+
def get(id)
|
7
|
+
target.detect { |record| record.id == id }
|
8
|
+
end
|
9
|
+
|
10
|
+
def get!(id)
|
11
|
+
get(id) || raise(Toy::NotFound.new(id))
|
12
|
+
end
|
13
|
+
|
14
|
+
def include?(record)
|
15
|
+
return false if record.nil?
|
16
|
+
target.include?(record)
|
17
|
+
end
|
18
|
+
|
19
|
+
def push(instance)
|
20
|
+
assert_type(instance)
|
21
|
+
assign_reference(instance)
|
22
|
+
self.target.push(instance)
|
23
|
+
end
|
24
|
+
alias :<< :push
|
25
|
+
|
26
|
+
def concat(*instances)
|
27
|
+
instances = instances.flatten
|
28
|
+
instances.each do |instance|
|
29
|
+
assert_type(instance)
|
30
|
+
assign_reference(instance)
|
31
|
+
end
|
32
|
+
self.target.concat instances
|
33
|
+
end
|
34
|
+
|
35
|
+
def replace(instances)
|
36
|
+
@target = instances.map do |instance|
|
37
|
+
instance = instance.is_a?(proxy_class) ? instance : proxy_class.load(instance)
|
38
|
+
assign_reference(instance)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def create(attrs={})
|
43
|
+
proxy_class.new(attrs).tap do |instance|
|
44
|
+
assign_reference(instance)
|
45
|
+
if instance.valid?
|
46
|
+
push(instance)
|
47
|
+
proxy_owner.save
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy(*args, &block)
|
53
|
+
ids = block_given? ? target.select(&block).map(&:id) : args.flatten
|
54
|
+
target.delete_if { |instance| ids.include?(instance.id) }
|
55
|
+
proxy_owner.save
|
56
|
+
end
|
57
|
+
|
58
|
+
def destroy_all
|
59
|
+
target.clear
|
60
|
+
proxy_owner.save
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def find_target
|
65
|
+
[]
|
66
|
+
end
|
67
|
+
|
68
|
+
def assign_reference(instance)
|
69
|
+
instance.parent_reference = proxy_owner
|
70
|
+
instance
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'toy/proxies/proxy'
|
2
|
+
|
3
|
+
module Toy
|
4
|
+
module Proxies
|
5
|
+
class List < Toy::Proxies::Proxy
|
6
|
+
def get(id)
|
7
|
+
if target_ids.include?(id)
|
8
|
+
proxy_class.get(id)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def get!(id)
|
13
|
+
get(id) || raise(Toy::NotFound.new(id))
|
14
|
+
end
|
15
|
+
|
16
|
+
def include?(record)
|
17
|
+
return false if record.nil?
|
18
|
+
target_ids.include?(record.id)
|
19
|
+
end
|
20
|
+
|
21
|
+
def push(record)
|
22
|
+
assert_type(record)
|
23
|
+
self.target_ids = target_ids + [record]
|
24
|
+
end
|
25
|
+
alias :<< :push
|
26
|
+
|
27
|
+
def concat(*records)
|
28
|
+
records = records.flatten
|
29
|
+
records.map { |record| assert_type(record) }
|
30
|
+
self.target_ids = target_ids + records
|
31
|
+
end
|
32
|
+
|
33
|
+
def replace(records)
|
34
|
+
reset
|
35
|
+
self.target_ids = records
|
36
|
+
end
|
37
|
+
|
38
|
+
def create(attrs={})
|
39
|
+
if proxy_options[:inverse_of]
|
40
|
+
attrs.update(:"#{proxy_options[:inverse_of]}_id" => proxy_owner.id)
|
41
|
+
end
|
42
|
+
proxy_class.create(attrs).tap do |record|
|
43
|
+
if record.persisted?
|
44
|
+
proxy_owner.reload
|
45
|
+
push(record)
|
46
|
+
proxy_owner.save
|
47
|
+
reset
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def destroy(*args, &block)
|
53
|
+
ids = block_given? ? target.select(&block).map(&:id) : args.flatten
|
54
|
+
proxy_class.destroy(*ids)
|
55
|
+
proxy_owner.reload
|
56
|
+
self.target_ids = target_ids - ids
|
57
|
+
proxy_owner.save
|
58
|
+
reset
|
59
|
+
end
|
60
|
+
|
61
|
+
def destroy_all
|
62
|
+
proxy_class.destroy(*target_ids)
|
63
|
+
self.target_ids = []
|
64
|
+
proxy_owner.save
|
65
|
+
reset
|
66
|
+
end
|
67
|
+
|
68
|
+
def reset
|
69
|
+
@target = nil
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
def find_target
|
74
|
+
return [] if target_ids.blank?
|
75
|
+
proxy_class.get_multi(target_ids)
|
76
|
+
end
|
77
|
+
|
78
|
+
def target_ids
|
79
|
+
proxy_owner.send(proxy_key)
|
80
|
+
end
|
81
|
+
|
82
|
+
def target_ids=(value)
|
83
|
+
ids = value.map do |item|
|
84
|
+
if item.is_a?(proxy_class)
|
85
|
+
item.id
|
86
|
+
elsif item.is_a?(Hash)
|
87
|
+
item['id']
|
88
|
+
else
|
89
|
+
item
|
90
|
+
end
|
91
|
+
end
|
92
|
+
proxy_owner.send(:"#{proxy_key}=", ids)
|
93
|
+
reset
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|