toystore 0.5
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/.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
|