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
@@ -0,0 +1,24 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module Date
|
4
|
+
def to_store(value, *)
|
5
|
+
if value.nil? || value == ''
|
6
|
+
nil
|
7
|
+
else
|
8
|
+
date = value.is_a?(::Date) || value.is_a?(::Time) ? value : ::Date.parse(value.to_s)
|
9
|
+
::Time.utc(date.year, date.month, date.day)
|
10
|
+
end
|
11
|
+
rescue
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_store(value, *)
|
16
|
+
value.to_date if value.present?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Date
|
23
|
+
extend Toy::Extensions::Date
|
24
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module Hash
|
4
|
+
def store_default
|
5
|
+
{}.with_indifferent_access
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_store(value, *)
|
9
|
+
value.nil? ? store_default : value.with_indifferent_access
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Hash
|
16
|
+
extend Toy::Extensions::Hash
|
17
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module Integer
|
4
|
+
def to_store(value, *)
|
5
|
+
value_to_i = value.to_i
|
6
|
+
if value_to_i == 0 && value != value_to_i
|
7
|
+
value.to_s =~ /^(0x|0b)?0+/ ? 0 : nil
|
8
|
+
else
|
9
|
+
value_to_i
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_store(value, *)
|
14
|
+
to_store(value)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Integer
|
21
|
+
extend Toy::Extensions::Integer
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module Object
|
4
|
+
module ClassMethods
|
5
|
+
def to_store(value, *)
|
6
|
+
value
|
7
|
+
end
|
8
|
+
|
9
|
+
def from_store(value, *)
|
10
|
+
value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module InstanceMethods
|
15
|
+
def to_store
|
16
|
+
self.class.to_store(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Object
|
24
|
+
extend Toy::Extensions::Object::ClassMethods
|
25
|
+
include Toy::Extensions::Object::InstanceMethods
|
26
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Toy
|
4
|
+
module Extensions
|
5
|
+
module Set
|
6
|
+
def store_default
|
7
|
+
[].to_set
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_store(value, *)
|
11
|
+
value.to_a
|
12
|
+
end
|
13
|
+
|
14
|
+
def from_store(value, *)
|
15
|
+
value.nil? ? store_default : value.to_set
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class Set
|
22
|
+
extend Toy::Extensions::Set
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module String
|
4
|
+
def to_store(value, *)
|
5
|
+
value.nil? ? nil : value.to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
def from_store(value, *)
|
9
|
+
value.nil? ? nil : value.to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class String
|
16
|
+
extend Toy::Extensions::String
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Toy
|
2
|
+
module Extensions
|
3
|
+
module Time
|
4
|
+
def to_store(value, *)
|
5
|
+
if value.nil? || value == ''
|
6
|
+
nil
|
7
|
+
else
|
8
|
+
time_class = ::Time.try(:zone).present? ? ::Time.zone : ::Time
|
9
|
+
time = value.is_a?(::Time) ? value : time_class.parse(value.to_s)
|
10
|
+
# strip milliseconds as Ruby does micro and bson does milli and rounding rounded wrong
|
11
|
+
at(time.to_i).utc if time
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def from_store(value, *)
|
16
|
+
value = to_store(value)
|
17
|
+
if ::Time.try(:zone).present? && value.present?
|
18
|
+
value.in_time_zone(::Time.zone)
|
19
|
+
else
|
20
|
+
value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Time
|
28
|
+
extend Toy::Extensions::Time
|
29
|
+
end
|
data/lib/toy/identity.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Toy
|
2
|
+
module Identity
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
key Toy.key_factory
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
def key(name_or_factory = :uuid)
|
11
|
+
@key_factory = case name_or_factory
|
12
|
+
when :uuid
|
13
|
+
UUIDKeyFactory.new
|
14
|
+
else
|
15
|
+
name_or_factory
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def next_key(object = nil)
|
20
|
+
@key_factory.next_key(object).tap do |key|
|
21
|
+
raise "Keys may not be nil" if key.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Toy
|
2
|
+
def self.identity_map
|
3
|
+
Thread.current[:toystore_identity_map] ||= {}
|
4
|
+
end
|
5
|
+
|
6
|
+
module IdentityMap
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
identity_map_on
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def identity_map
|
15
|
+
Toy.identity_map
|
16
|
+
end
|
17
|
+
|
18
|
+
def identity_map_on?
|
19
|
+
@identity_map_on == true
|
20
|
+
end
|
21
|
+
|
22
|
+
def identity_map_off?
|
23
|
+
!identity_map_on?
|
24
|
+
end
|
25
|
+
|
26
|
+
def identity_map_on
|
27
|
+
@identity_map_on = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def identity_map_off
|
31
|
+
@identity_map_on = false
|
32
|
+
end
|
33
|
+
|
34
|
+
def without_identity_map(&block)
|
35
|
+
begin
|
36
|
+
original_identity_map_on = @identity_map_on
|
37
|
+
identity_map_off
|
38
|
+
yield
|
39
|
+
ensure
|
40
|
+
@identity_map_on = original_identity_map_on
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def get(id)
|
45
|
+
get_from_identity_map(id) || super
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_from_identity_map(id)
|
49
|
+
return nil unless identity_map_on?
|
50
|
+
key = store_key(id)
|
51
|
+
if record = identity_map[key]
|
52
|
+
logger.debug("ToyStore IMG #{key.inspect}")
|
53
|
+
record
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def load(attrs)
|
58
|
+
return nil if attrs.nil?
|
59
|
+
|
60
|
+
if instance = identity_map[store_key(attrs['id'])]
|
61
|
+
instance
|
62
|
+
else
|
63
|
+
super.tap { |doc| doc.add_to_identity_map }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def identity_map
|
69
|
+
Toy.identity_map
|
70
|
+
end
|
71
|
+
|
72
|
+
def save(*)
|
73
|
+
super.tap do |result|
|
74
|
+
add_to_identity_map if result
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def delete(*)
|
79
|
+
super.tap { remove_from_identity_map }
|
80
|
+
end
|
81
|
+
|
82
|
+
def add_to_identity_map
|
83
|
+
return unless self.class.identity_map_on?
|
84
|
+
key = store_key
|
85
|
+
identity_map[key] = self
|
86
|
+
logger.debug("ToyStore IMS #{key.inspect}")
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_from_identity_map
|
90
|
+
return unless self.class.identity_map_on?
|
91
|
+
key = store_key
|
92
|
+
identity_map.delete(key)
|
93
|
+
logger.debug("ToyStore IMD #{key.inspect}")
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
def has_embedded_objects?
|
98
|
+
self.class.embedded_lists.any?
|
99
|
+
end
|
100
|
+
|
101
|
+
def each_embedded_object(&block)
|
102
|
+
if has_embedded_objects?
|
103
|
+
self.class.embedded_lists.keys.inject([]) do |objects, name|
|
104
|
+
objects.concat(send(name).to_a.compact)
|
105
|
+
end.each { |object| block.call(object) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
data/lib/toy/index.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
module Toy
|
2
|
+
class Index
|
3
|
+
attr_accessor :model, :name
|
4
|
+
|
5
|
+
def initialize(model, name)
|
6
|
+
@model, @name = model, name.to_sym
|
7
|
+
raise(ArgumentError, "No attribute #{name} for index") unless model.attribute?(name)
|
8
|
+
|
9
|
+
model.indices[name] = self
|
10
|
+
model.send(:include, IndexCallbacks)
|
11
|
+
create_finders
|
12
|
+
end
|
13
|
+
|
14
|
+
def eql?(other)
|
15
|
+
self.class.eql?(other.class) &&
|
16
|
+
model == other.model &&
|
17
|
+
name == other.name
|
18
|
+
end
|
19
|
+
alias :== :eql?
|
20
|
+
|
21
|
+
def key(value)
|
22
|
+
sha_value = Digest::SHA1.hexdigest(Array.wrap(value).sort.join('')) # sorted for predictability
|
23
|
+
[model.name, name, sha_value].join(':')
|
24
|
+
end
|
25
|
+
|
26
|
+
module IndexCallbacks
|
27
|
+
extend ActiveSupport::Concern
|
28
|
+
|
29
|
+
included do
|
30
|
+
after_create :index_create
|
31
|
+
after_update :index_update
|
32
|
+
after_destroy :index_destroy
|
33
|
+
end
|
34
|
+
|
35
|
+
def index_create
|
36
|
+
indices.each_key do |name|
|
37
|
+
create_index(name, send(name), id)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def index_update
|
42
|
+
indices.each_key do |name|
|
43
|
+
if send(:"#{name}_changed?")
|
44
|
+
destroy_index(name, send(:"#{name}_was"), id)
|
45
|
+
create_index(name, send(name), id)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def index_destroy
|
51
|
+
indices.each_key do |name|
|
52
|
+
destroy_index(name, send(name), id)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
def create_finders
|
59
|
+
model.class_eval """
|
60
|
+
def self.first_by_#{name}(value)
|
61
|
+
get(get_index(:#{name}, value)[0])
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.first_or_new_by_#{name}(value)
|
65
|
+
first_by_#{name}(value) || new(:#{name} => value)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.first_or_create_by_#{name}(value)
|
69
|
+
first_by_#{name}(value) || create(:#{name} => value)
|
70
|
+
end
|
71
|
+
"""
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
data/lib/toy/indices.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Toy
|
2
|
+
module Indices
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def indices
|
7
|
+
@indices ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def index(name)
|
11
|
+
Index.new(self, name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def index_key(name, value)
|
15
|
+
if index = indices[name.to_sym]
|
16
|
+
index.key(value)
|
17
|
+
else
|
18
|
+
raise(ArgumentError, "Index for #{name} does not exist")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_index(name, value)
|
23
|
+
key = index_key(name, value)
|
24
|
+
store.read(key) || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_index(name, value, id)
|
28
|
+
key = index_key(name, value)
|
29
|
+
ids = get_index(name, value)
|
30
|
+
ids.push(id) unless ids.include?(id)
|
31
|
+
store.write(key, ids)
|
32
|
+
end
|
33
|
+
|
34
|
+
def destroy_index(name, value, id)
|
35
|
+
key = index_key(name, value)
|
36
|
+
ids = get_index(name, value)
|
37
|
+
ids.delete(id)
|
38
|
+
store.write(key, ids)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
module InstanceMethods
|
43
|
+
def indices
|
44
|
+
self.class.indices
|
45
|
+
end
|
46
|
+
|
47
|
+
def create_index(*args)
|
48
|
+
self.class.create_index(*args)
|
49
|
+
end
|
50
|
+
|
51
|
+
def destroy_index(*args)
|
52
|
+
self.class.destroy_index(*args)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|