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.
Files changed (113) hide show
  1. data/.autotest +11 -0
  2. data/.bundle/config +2 -0
  3. data/.gitignore +6 -0
  4. data/Gemfile +10 -0
  5. data/Gemfile.lock +49 -0
  6. data/LICENSE +9 -0
  7. data/LOGGING.rdoc +16 -0
  8. data/README.rdoc +13 -0
  9. data/Rakefile +7 -0
  10. data/examples/memcached.rb +20 -0
  11. data/examples/memory.rb +20 -0
  12. data/examples/models.rb +51 -0
  13. data/examples/redis.rb +20 -0
  14. data/lib/toy.rb +81 -0
  15. data/lib/toy/attribute.rb +73 -0
  16. data/lib/toy/attributes.rb +137 -0
  17. data/lib/toy/caching.rb +20 -0
  18. data/lib/toy/callbacks.rb +48 -0
  19. data/lib/toy/collection.rb +55 -0
  20. data/lib/toy/connection.rb +28 -0
  21. data/lib/toy/dirty.rb +47 -0
  22. data/lib/toy/dolly.rb +30 -0
  23. data/lib/toy/embedded_list.rb +45 -0
  24. data/lib/toy/embedded_lists.rb +68 -0
  25. data/lib/toy/equality.rb +19 -0
  26. data/lib/toy/exceptions.rb +29 -0
  27. data/lib/toy/extensions/array.rb +22 -0
  28. data/lib/toy/extensions/boolean.rb +43 -0
  29. data/lib/toy/extensions/date.rb +24 -0
  30. data/lib/toy/extensions/float.rb +13 -0
  31. data/lib/toy/extensions/hash.rb +17 -0
  32. data/lib/toy/extensions/integer.rb +22 -0
  33. data/lib/toy/extensions/nil_class.rb +17 -0
  34. data/lib/toy/extensions/object.rb +26 -0
  35. data/lib/toy/extensions/set.rb +23 -0
  36. data/lib/toy/extensions/string.rb +17 -0
  37. data/lib/toy/extensions/time.rb +29 -0
  38. data/lib/toy/identity.rb +26 -0
  39. data/lib/toy/identity/abstract_key_factory.rb +10 -0
  40. data/lib/toy/identity/uuid_key_factory.rb +9 -0
  41. data/lib/toy/identity_map.rb +109 -0
  42. data/lib/toy/index.rb +74 -0
  43. data/lib/toy/indices.rb +56 -0
  44. data/lib/toy/inspect.rb +12 -0
  45. data/lib/toy/list.rb +46 -0
  46. data/lib/toy/lists.rb +37 -0
  47. data/lib/toy/logger.rb +26 -0
  48. data/lib/toy/mass_assignment_security.rb +16 -0
  49. data/lib/toy/persistence.rb +138 -0
  50. data/lib/toy/plugins.rb +23 -0
  51. data/lib/toy/proxies/embedded_list.rb +74 -0
  52. data/lib/toy/proxies/list.rb +97 -0
  53. data/lib/toy/proxies/proxy.rb +59 -0
  54. data/lib/toy/querying.rb +57 -0
  55. data/lib/toy/reference.rb +134 -0
  56. data/lib/toy/references.rb +19 -0
  57. data/lib/toy/serialization.rb +81 -0
  58. data/lib/toy/store.rb +36 -0
  59. data/lib/toy/timestamps.rb +22 -0
  60. data/lib/toy/validations.rb +45 -0
  61. data/lib/toy/version.rb +3 -0
  62. data/lib/toystore.rb +1 -0
  63. data/spec/helper.rb +35 -0
  64. data/spec/spec.opts +3 -0
  65. data/spec/support/constants.rb +41 -0
  66. data/spec/support/identity_map_matcher.rb +20 -0
  67. data/spec/support/name_and_number_key_factory.rb +5 -0
  68. data/spec/toy/attribute_spec.rb +176 -0
  69. data/spec/toy/attributes_spec.rb +394 -0
  70. data/spec/toy/caching_spec.rb +62 -0
  71. data/spec/toy/callbacks_spec.rb +97 -0
  72. data/spec/toy/connection_spec.rb +47 -0
  73. data/spec/toy/dirty_spec.rb +99 -0
  74. data/spec/toy/dolly_spec.rb +76 -0
  75. data/spec/toy/embedded_list_spec.rb +607 -0
  76. data/spec/toy/embedded_lists_spec.rb +172 -0
  77. data/spec/toy/equality_spec.rb +46 -0
  78. data/spec/toy/exceptions_spec.rb +18 -0
  79. data/spec/toy/extensions/array_spec.rb +25 -0
  80. data/spec/toy/extensions/boolean_spec.rb +41 -0
  81. data/spec/toy/extensions/date_spec.rb +48 -0
  82. data/spec/toy/extensions/float_spec.rb +14 -0
  83. data/spec/toy/extensions/hash_spec.rb +21 -0
  84. data/spec/toy/extensions/integer_spec.rb +29 -0
  85. data/spec/toy/extensions/nil_class_spec.rb +14 -0
  86. data/spec/toy/extensions/set_spec.rb +27 -0
  87. data/spec/toy/extensions/string_spec.rb +28 -0
  88. data/spec/toy/extensions/time_spec.rb +94 -0
  89. data/spec/toy/identity/abstract_key_factory_spec.rb +7 -0
  90. data/spec/toy/identity/uuid_key_factory_spec.rb +7 -0
  91. data/spec/toy/identity_map_spec.rb +150 -0
  92. data/spec/toy/identity_spec.rb +52 -0
  93. data/spec/toy/index_spec.rb +230 -0
  94. data/spec/toy/indices_spec.rb +141 -0
  95. data/spec/toy/inspect_spec.rb +15 -0
  96. data/spec/toy/list_spec.rb +576 -0
  97. data/spec/toy/lists_spec.rb +95 -0
  98. data/spec/toy/logger_spec.rb +33 -0
  99. data/spec/toy/mass_assignment_security_spec.rb +116 -0
  100. data/spec/toy/persistence_spec.rb +312 -0
  101. data/spec/toy/plugins_spec.rb +39 -0
  102. data/spec/toy/querying_spec.rb +162 -0
  103. data/spec/toy/reference_spec.rb +400 -0
  104. data/spec/toy/references_spec.rb +86 -0
  105. data/spec/toy/serialization_spec.rb +354 -0
  106. data/spec/toy/store_spec.rb +41 -0
  107. data/spec/toy/timestamps_spec.rb +63 -0
  108. data/spec/toy/validations_spec.rb +171 -0
  109. data/spec/toy_spec.rb +26 -0
  110. data/specs.watchr +52 -0
  111. data/test/lint_test.rb +40 -0
  112. data/toystore.gemspec +24 -0
  113. 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,13 @@
1
+ module Toy
2
+ module Extensions
3
+ module Float
4
+ def to_store(value, *)
5
+ value.nil? ? nil : value.to_f
6
+ end
7
+ end
8
+ end
9
+ end
10
+
11
+ class Float
12
+ extend Toy::Extensions::Float
13
+ 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,17 @@
1
+ module Toy
2
+ module Extensions
3
+ module NilClass
4
+ def to_store(value, *)
5
+ value
6
+ end
7
+
8
+ def from_store(value, *)
9
+ value
10
+ end
11
+ end
12
+ end
13
+ end
14
+
15
+ class NilClass
16
+ include Toy::Extensions::NilClass
17
+ 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
@@ -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,10 @@
1
+ module Toy
2
+ module Identity
3
+ class AbstractKeyFactory
4
+ def next_key(object)
5
+ raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,9 @@
1
+ module Toy
2
+ module Identity
3
+ class UUIDKeyFactory < AbstractKeyFactory
4
+ def next_key(object)
5
+ SimpleUUID::UUID.new.to_guid
6
+ end
7
+ end
8
+ end
9
+ 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
@@ -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
@@ -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