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
data/lib/toy/caching.rb
ADDED
@@ -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
|
data/lib/toy/dirty.rb
ADDED
@@ -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
|
data/lib/toy/dolly.rb
ADDED
@@ -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
|
data/lib/toy/equality.rb
ADDED
@@ -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
|