toystore 0.5

Sign up to get free protection for your applications and to get access to all the features.
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