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,20 @@
1
+ module Toy
2
+ module Caching
3
+ extend ActiveSupport::Concern
4
+
5
+ module InstanceMethods
6
+ def cache_key(*suffixes)
7
+ cache_key = case
8
+ when new_record?
9
+ "#{self.class.name}:new"
10
+ when timestamp = self[:updated_at]
11
+ "#{self.class.name}:#{id}-#{timestamp.utc.to_s(:number)}"
12
+ else
13
+ "#{self.class.name}:#{id}"
14
+ end
15
+ cache_key += ":#{suffixes.join(':')}" unless suffixes.empty?
16
+ cache_key
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ module Toy
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+ include ActiveSupport::Callbacks
5
+
6
+ included do
7
+ extend ActiveModel::Callbacks
8
+ define_model_callbacks :save, :create, :update, :destroy
9
+ end
10
+
11
+ module InstanceMethods
12
+ def save(*)
13
+ run_callbacks(:save) { super }
14
+ end
15
+
16
+ def destroy
17
+ run_callbacks(:destroy) { super }
18
+ end
19
+
20
+ def run_callbacks(callback, &block)
21
+ callback = :create if callback == :update && !persisted?
22
+
23
+ embedded_records = self.class.embedded_lists.keys.inject([]) do |records, key|
24
+ records += send(key).target
25
+ end
26
+
27
+ block = embedded_records.inject(block) do |chain, record|
28
+ if record.class.respond_to?("_#{callback}_callbacks")
29
+ lambda { record.run_callbacks(callback, &chain) }
30
+ else
31
+ chain
32
+ end
33
+ end
34
+ super callback, &block
35
+ end
36
+
37
+ private
38
+
39
+ def create
40
+ run_callbacks(:create) { super }
41
+ end
42
+
43
+ def update
44
+ run_callbacks(:update) { super }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,55 @@
1
+ module Toy
2
+ module Collection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ attr_accessor :model, :name, :options
7
+ end
8
+
9
+ def initialize(model, name, *args, &block)
10
+ @model = model
11
+ @name = name.to_sym
12
+ @options = args.extract_options!
13
+ @type = args.shift
14
+
15
+ model.send(list_method)[name] = self
16
+
17
+ options[:extensions] = modularized_extensions(block, options[:extensions])
18
+ after_initialize
19
+ end
20
+
21
+ def type
22
+ @type ||= name.to_s.classify.constantize
23
+ end
24
+
25
+ def instance_variable
26
+ @instance_variable ||= :"@_#{name}"
27
+ end
28
+
29
+ def new_proxy(owner)
30
+ proxy_class.new(self, owner)
31
+ end
32
+
33
+ def extensions
34
+ options[:extensions]
35
+ end
36
+
37
+ def eql?(other)
38
+ self.class.eql?(other.class) &&
39
+ model == other.model &&
40
+ name == other.name
41
+ end
42
+ alias :== :eql?
43
+
44
+ private
45
+ def proxy_class
46
+ raise('Not Implemented')
47
+ end
48
+
49
+ def modularized_extensions(*extensions)
50
+ extensions.flatten.compact.map do |extension|
51
+ Proc === extension ? Module.new(&extension) : extension
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,28 @@
1
+ module Toy
2
+ module Connection
3
+ def logger
4
+ @@logger ||= init_default_logger
5
+ end
6
+
7
+ def logger=(logger)
8
+ @@logger = logger
9
+ end
10
+
11
+ def key_factory=(key_factory)
12
+ @@key_factory = key_factory
13
+ end
14
+
15
+ def key_factory
16
+ @@key_factory ||= Toy::Identity::UUIDKeyFactory.new
17
+ end
18
+
19
+ def init_default_logger
20
+ if Object.const_defined?("RAILS_DEFAULT_LOGGER")
21
+ @@logger = Object.const_get("RAILS_DEFAULT_LOGGER")
22
+ else
23
+ require 'logger'
24
+ @@logger = ::Logger.new(STDOUT)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,47 @@
1
+ module Toy
2
+ module Dirty
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Dirty
5
+
6
+ module InstanceMethods
7
+ def initialize(*)
8
+ super
9
+ if new_record?
10
+ # never register initial id assignment as a change
11
+ @changed_attributes.delete('id')
12
+ else
13
+ @previously_changed = {}
14
+ @changed_attributes.clear if @changed_attributes
15
+ end
16
+ end
17
+
18
+ def initialize_copy(*)
19
+ super.tap do
20
+ @previously_changed = {}
21
+ @changed_attributes = {}
22
+ end
23
+ end
24
+
25
+ def reload
26
+ super.tap do
27
+ @previously_changed = {}
28
+ @changed_attributes = {}
29
+ end
30
+ end
31
+
32
+ def save(*)
33
+ super.tap do
34
+ @previously_changed = changes
35
+ @changed_attributes.clear if @changed_attributes
36
+ end
37
+ end
38
+
39
+ def write_attribute(name, value)
40
+ name = name.to_s
41
+ current = read_attribute(name)
42
+ attribute_will_change!(name) if current != value
43
+ super
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,30 @@
1
+ module Toy
2
+ module Dolly
3
+ extend ActiveSupport::Concern
4
+
5
+ def initialize_copy(other)
6
+ @_new_record = true
7
+ @_destroyed = false
8
+ @attributes = {}.with_indifferent_access
9
+
10
+ self.class.embedded_lists.each do |name, list|
11
+ instance_variable_set(list.instance_variable, nil)
12
+ end
13
+
14
+ self.class.lists.each do |name, list|
15
+ instance_variable_set(list.instance_variable, nil)
16
+ end
17
+
18
+ other.attributes.except('id').each do |key, value|
19
+ value = value.duplicable? ? value.clone : value
20
+ send("#{key}=", value)
21
+ end
22
+
23
+ other.class.embedded_lists.keys.each do |name|
24
+ send("#{name}=", other.send(name).map(&:clone))
25
+ end
26
+
27
+ write_attribute(:id, self.class.next_key(self))
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,45 @@
1
+ require 'toy/proxies/embedded_list'
2
+
3
+ module Toy
4
+ class EmbeddedList
5
+ include Toy::Collection
6
+
7
+ def after_initialize
8
+ create_accessors
9
+ end
10
+
11
+ private
12
+ def create_accessors
13
+ model.class_eval """
14
+ def #{name}
15
+ #{instance_variable} ||= self.class.#{list_method}[:#{name}].new_proxy(self)
16
+ end
17
+
18
+ def #{name}=(records)
19
+ #{name}.replace(records)
20
+ end
21
+
22
+ def #{name.to_s.singularize}_attributes=(attrs)
23
+ self.#{name} = attrs.map do |value|
24
+ value = value.is_a?(Hash) ? value : value[1]
25
+ #{type}.new(value)
26
+ end
27
+ end
28
+
29
+ def #{name.to_s.singularize}_attributes
30
+ #{name}.map(&:attributes)
31
+ end
32
+ """
33
+
34
+ type.class_eval { attr_accessor :parent_reference }
35
+ end
36
+
37
+ def proxy_class
38
+ Toy::Proxies::EmbeddedList
39
+ end
40
+
41
+ def list_method
42
+ :embedded_lists
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,68 @@
1
+ module Toy
2
+ module EmbeddedLists
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def embedded_lists
7
+ @embedded_lists ||= {}
8
+ end
9
+
10
+ def embedded_list?(key)
11
+ embedded_lists.keys.include?(key.to_sym)
12
+ end
13
+
14
+ def parent_reference_module
15
+ @parent_reference_module ||= begin
16
+ mod = Module.new
17
+ include mod
18
+ mod
19
+ end
20
+ end
21
+
22
+ def parent_references
23
+ @parent_references ||= []
24
+ end
25
+
26
+ def parent_reference?(key)
27
+ parent_references.include?(key.to_sym)
28
+ end
29
+
30
+ def parent_reference(*names)
31
+ names.flatten.each do |name|
32
+ parent_references << name
33
+ parent_reference_module.module_eval <<-CODE
34
+ def #{name}
35
+ parent_reference
36
+ end
37
+
38
+ def #{name}?
39
+ parent_reference.present?
40
+ end
41
+ CODE
42
+ end
43
+ end
44
+
45
+ # @examples
46
+ # list :moves # assumes Move
47
+ # list :moves, :dependent => true # assumes Move
48
+ # list :recent_moves, Move # uses Move
49
+ # list :recent_moves, Move, :dependent => true # uses Move
50
+ #
51
+ # embedded_list :moves do
52
+ # def recent
53
+ # target.select { |t| t.recent? }
54
+ # end
55
+ # end
56
+ #
57
+ # module RecentExtension
58
+ # def recent
59
+ # target.select { |t| t.recent? }
60
+ # end
61
+ # end
62
+ # embedded_list :moves, :extensions => [RecentExtension]
63
+ def embedded_list(name, *args, &block)
64
+ EmbeddedList.new(self, name, *args, &block)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ module Toy
2
+ module Equality
3
+ def eql?(other)
4
+ return true if self.class.eql?(other.class) && id == other.id
5
+ return true if other.respond_to?(:target) &&
6
+ self.class.eql?(other.target.class) &&
7
+ id == other.target.id
8
+ false
9
+ end
10
+ alias :== :eql?
11
+
12
+ def equal?(other)
13
+ if other.respond_to?(:proxy_respond_to?) && other.respond_to?(:target)
14
+ other = other.target
15
+ end
16
+ super other
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,29 @@
1
+ module Toy
2
+ class Error < StandardError; end
3
+
4
+ class RecordInvalidError < Error
5
+ attr_reader :record
6
+ def initialize(record)
7
+ @record = record
8
+ super("Invalid record: #{@record.errors.full_messages.to_sentence}")
9
+ end
10
+ end
11
+
12
+ class NotFound < Error
13
+ def initialize(id)
14
+ super("Could not find document with id: #{id.inspect}")
15
+ end
16
+ end
17
+
18
+ class UndefinedLock < Error
19
+ def initialize(klass, name)
20
+ super("Undefined lock :#{name} for class #{klass.name}")
21
+ end
22
+ end
23
+
24
+ class AdapterNoLocky < Error
25
+ def initialize(adapter)
26
+ super("#{adapter.name.to_s.capitalize} adapter does not support locking")
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ module Toy
2
+ module Extensions
3
+ module Array
4
+ def store_default
5
+ []
6
+ end
7
+
8
+ def to_store(value, *)
9
+ value = value.respond_to?(:lines) ? value.lines : value
10
+ value.to_a
11
+ end
12
+
13
+ def from_store(value, *)
14
+ value || store_default
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ class Array
21
+ extend Toy::Extensions::Array
22
+ end
@@ -0,0 +1,43 @@
1
+ module Toy
2
+ module Extensions
3
+ module Boolean
4
+ Mapping = {
5
+ true => true,
6
+ 'true' => true,
7
+ 'TRUE' => true,
8
+ 'True' => true,
9
+ 't' => true,
10
+ 'T' => true,
11
+ '1' => true,
12
+ 1 => true,
13
+ 1.0 => true,
14
+ false => false,
15
+ 'false' => false,
16
+ 'FALSE' => false,
17
+ 'False' => false,
18
+ 'f' => false,
19
+ 'F' => false,
20
+ '0' => false,
21
+ 0 => false,
22
+ 0.0 => false,
23
+ nil => nil
24
+ }
25
+
26
+ def to_store(value, *)
27
+ if value.is_a?(Boolean)
28
+ value
29
+ else
30
+ Mapping[value]
31
+ end
32
+ end
33
+
34
+ def from_store(value, *)
35
+ value.nil? ? nil : !!value
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ class Boolean
42
+ extend Toy::Extensions::Boolean
43
+ end