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,59 @@
|
|
1
|
+
module Toy
|
2
|
+
module Proxies
|
3
|
+
class Proxy
|
4
|
+
include Enumerable
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
def_delegator :@list, :type, :proxy_class
|
8
|
+
def_delegator :@list, :key, :proxy_key
|
9
|
+
def_delegator :@list, :options, :proxy_options
|
10
|
+
|
11
|
+
alias :proxy_respond_to? :respond_to?
|
12
|
+
alias :proxy_extend :extend
|
13
|
+
|
14
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
|
15
|
+
|
16
|
+
def initialize(list, owner)
|
17
|
+
@list, @owner = list, owner
|
18
|
+
list.extensions.each { |extension| proxy_extend(extension) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def proxy_owner
|
22
|
+
@owner
|
23
|
+
end
|
24
|
+
|
25
|
+
def each
|
26
|
+
target.each { |i| yield(i) }
|
27
|
+
end
|
28
|
+
|
29
|
+
def eql?(other)
|
30
|
+
target == other
|
31
|
+
end
|
32
|
+
alias :== :eql?
|
33
|
+
|
34
|
+
def target
|
35
|
+
@target ||= find_target
|
36
|
+
end
|
37
|
+
alias :to_a :target
|
38
|
+
|
39
|
+
def assert_type(record)
|
40
|
+
unless record.is_a?(proxy_class)
|
41
|
+
raise(ArgumentError, "#{proxy_class} expected, but was #{record.class}")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to?(*args)
|
46
|
+
proxy_respond_to?(*args) || target.respond_to?(*args)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def find_target
|
51
|
+
raise('Not Implemented')
|
52
|
+
end
|
53
|
+
|
54
|
+
def method_missing(method, *args, &block)
|
55
|
+
target.send(method, *args, &block)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/toy/querying.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
module Toy
|
2
|
+
module Querying
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def get(id)
|
7
|
+
key = store_key(id)
|
8
|
+
|
9
|
+
if has_cache?
|
10
|
+
value = cache.read(key)
|
11
|
+
log_operation('RTG', cache, key, value)
|
12
|
+
end
|
13
|
+
|
14
|
+
if value.nil?
|
15
|
+
value = store.read(key)
|
16
|
+
log_operation('GET', store, key, value)
|
17
|
+
|
18
|
+
if has_cache?
|
19
|
+
cache.write(key, value)
|
20
|
+
log_operation('RTS', cache, key, value)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
load(value)
|
25
|
+
end
|
26
|
+
|
27
|
+
def get!(id)
|
28
|
+
get(id) || raise(Toy::NotFound.new(id))
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_multi(*ids)
|
32
|
+
ids.flatten.map { |id| get(id) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_or_new(id)
|
36
|
+
get(id) || new(:id => id)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_or_create(id)
|
40
|
+
get(id) || create(:id => id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def key?(id)
|
44
|
+
key = store_key(id)
|
45
|
+
value = store.key?(key)
|
46
|
+
log_operation('KEY', store, key, value)
|
47
|
+
value
|
48
|
+
end
|
49
|
+
alias :has_key? :key?
|
50
|
+
|
51
|
+
def load(attrs)
|
52
|
+
return nil if attrs.nil?
|
53
|
+
allocate.initialize_from_database(attrs)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
module Toy
|
2
|
+
class Reference
|
3
|
+
attr_accessor :model, :name, :options
|
4
|
+
|
5
|
+
def initialize(model, name, *args)
|
6
|
+
@model = model
|
7
|
+
@name = name.to_sym
|
8
|
+
@options = args.extract_options!
|
9
|
+
@type = args.shift
|
10
|
+
|
11
|
+
model.references[name] = self
|
12
|
+
model.attribute(key, String)
|
13
|
+
create_accessors
|
14
|
+
end
|
15
|
+
|
16
|
+
def type
|
17
|
+
@type ||= name.to_s.classify.constantize
|
18
|
+
end
|
19
|
+
|
20
|
+
def key
|
21
|
+
@key ||= :"#{name.to_s.singularize}_id"
|
22
|
+
end
|
23
|
+
|
24
|
+
def instance_variable
|
25
|
+
@instance_variable ||= :"@_#{name}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def new_proxy(owner)
|
29
|
+
ReferenceProxy.new(self, owner)
|
30
|
+
end
|
31
|
+
|
32
|
+
def eql?(other)
|
33
|
+
self.class.eql?(other.class) &&
|
34
|
+
model == other.model &&
|
35
|
+
name == other.name
|
36
|
+
end
|
37
|
+
alias :== :eql?
|
38
|
+
|
39
|
+
class ReferenceProxy
|
40
|
+
extend Forwardable
|
41
|
+
|
42
|
+
def_delegator :@reference, :type, :proxy_class
|
43
|
+
def_delegator :@reference, :key, :proxy_key
|
44
|
+
alias_method :proxy_respond_to?, :respond_to?
|
45
|
+
instance_methods.each { |m| undef_method m unless m.to_s =~ /^(?:nil\?|send|object_id|to_a)$|^__|proxy_/ }
|
46
|
+
|
47
|
+
def initialize(reference, owner)
|
48
|
+
@reference, @owner = reference, owner
|
49
|
+
end
|
50
|
+
|
51
|
+
def proxy_owner
|
52
|
+
@owner
|
53
|
+
end
|
54
|
+
|
55
|
+
def target
|
56
|
+
return nil if target_id.blank?
|
57
|
+
@target ||= proxy_class.get(target_id)
|
58
|
+
end
|
59
|
+
|
60
|
+
def reset
|
61
|
+
@target = nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def replace(record)
|
65
|
+
if record.nil?
|
66
|
+
reset
|
67
|
+
self.target_id = nil
|
68
|
+
else
|
69
|
+
assert_type(record)
|
70
|
+
@target = record
|
71
|
+
self.target_id = record.id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def create(attrs={})
|
76
|
+
proxy_class.create(attrs).tap do |record|
|
77
|
+
if record.persisted?
|
78
|
+
self.target_id = record.id
|
79
|
+
proxy_owner.save
|
80
|
+
reset
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def build(attrs={})
|
86
|
+
proxy_class.new(attrs).tap do |record|
|
87
|
+
self.target_id = record.id
|
88
|
+
reset
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Does the proxy or its \target respond to +symbol+?
|
93
|
+
def respond_to?(*args)
|
94
|
+
proxy_respond_to?(*args) || target.respond_to?(*args)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def assert_type(record)
|
99
|
+
unless record.instance_of?(proxy_class)
|
100
|
+
raise(ArgumentError, "#{proxy_class} expected, but was #{record.class}")
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def target_id
|
105
|
+
proxy_owner.send(proxy_key)
|
106
|
+
end
|
107
|
+
|
108
|
+
def target_id=(value)
|
109
|
+
proxy_owner.send("#{proxy_key}=", value)
|
110
|
+
end
|
111
|
+
|
112
|
+
def method_missing(method, *args, &block)
|
113
|
+
target.send(method, *args, &block)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
def create_accessors
|
119
|
+
model.class_eval """
|
120
|
+
def #{name}
|
121
|
+
#{instance_variable} ||= self.class.references[:#{name}].new_proxy(self)
|
122
|
+
end
|
123
|
+
|
124
|
+
def #{name}=(record)
|
125
|
+
#{name}.replace(record)
|
126
|
+
end
|
127
|
+
|
128
|
+
def #{name}?
|
129
|
+
#{name}.present?
|
130
|
+
end
|
131
|
+
"""
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Toy
|
2
|
+
module References
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def references
|
7
|
+
@references ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def reference?(key)
|
11
|
+
references.keys.include?(key.to_sym)
|
12
|
+
end
|
13
|
+
|
14
|
+
def reference(name, *args)
|
15
|
+
Reference.new(self, name, *args)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Toy
|
2
|
+
module Serialization
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Serializers::JSON
|
5
|
+
include ActiveModel::Serializers::Xml
|
6
|
+
|
7
|
+
def serializable_attributes
|
8
|
+
attributes.keys.sort.map(&:to_sym)
|
9
|
+
end
|
10
|
+
|
11
|
+
def serializable_hash(options = nil)
|
12
|
+
hash = {}
|
13
|
+
options ||= {}
|
14
|
+
options[:only] = Array.wrap(options[:only]).map(&:to_sym)
|
15
|
+
options[:except] = Array.wrap(options[:except]).map(&:to_sym)
|
16
|
+
|
17
|
+
serializable_stuff = serializable_attributes.map(&:to_sym) + self.class.embedded_lists.keys.map(&:to_sym)
|
18
|
+
|
19
|
+
if options[:only].any?
|
20
|
+
serializable_stuff &= options[:only]
|
21
|
+
elsif options[:except].any?
|
22
|
+
serializable_stuff -= options[:except]
|
23
|
+
end
|
24
|
+
|
25
|
+
serializable_stuff += Array.wrap(options[:methods]).map(&:to_sym).select do |method|
|
26
|
+
respond_to?(method)
|
27
|
+
end
|
28
|
+
|
29
|
+
serializable_stuff.each do |name|
|
30
|
+
value = send(name)
|
31
|
+
hash[name.to_s] = if value.is_a?(Array)
|
32
|
+
value.map { |v| v.respond_to?(:serializable_hash) ? v.serializable_hash : v }
|
33
|
+
elsif value.respond_to?(:serializable_hash)
|
34
|
+
value.serializable_hash
|
35
|
+
else
|
36
|
+
value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
serializable_add_includes(options) do |association, records, opts|
|
41
|
+
hash[association] = records.is_a?(Enumerable) ?
|
42
|
+
records.map { |r| r.serializable_hash(opts) } :
|
43
|
+
records.serializable_hash(opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
hash
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
# Add associations specified via the <tt>:includes</tt> option.
|
51
|
+
# Expects a block that takes as arguments:
|
52
|
+
# +association+ - name of the association
|
53
|
+
# +records+ - the association record(s) to be serialized
|
54
|
+
# +opts+ - options for the association records
|
55
|
+
def serializable_add_includes(options = {})
|
56
|
+
return unless include_associations = options.delete(:include)
|
57
|
+
|
58
|
+
base_only_or_except = { :except => options[:except],
|
59
|
+
:only => options[:only] }
|
60
|
+
|
61
|
+
include_has_options = include_associations.is_a?(Hash)
|
62
|
+
associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
|
63
|
+
|
64
|
+
for association in associations
|
65
|
+
records = if self.class.list?(association)
|
66
|
+
send(association).to_a
|
67
|
+
elsif self.class.reference?(association) || self.class.parent_reference?(association)
|
68
|
+
send(association)
|
69
|
+
end
|
70
|
+
|
71
|
+
unless records.nil?
|
72
|
+
association_options = include_has_options ? include_associations[association] : base_only_or_except
|
73
|
+
opts = options.merge(association_options)
|
74
|
+
yield(association, records, opts)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
options[:include] = include_associations
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/toy/store.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Toy
|
2
|
+
module Store
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
extend ActiveModel::Naming
|
7
|
+
include ActiveModel::Conversion
|
8
|
+
include Attributes
|
9
|
+
include Identity
|
10
|
+
include Persistence
|
11
|
+
include MassAssignmentSecurity
|
12
|
+
include Plugins
|
13
|
+
include Dolly
|
14
|
+
include Dirty
|
15
|
+
include Equality
|
16
|
+
include Inspect
|
17
|
+
include Querying
|
18
|
+
|
19
|
+
include Callbacks
|
20
|
+
include Validations
|
21
|
+
include Serialization
|
22
|
+
include Timestamps
|
23
|
+
|
24
|
+
include Lists
|
25
|
+
include EmbeddedLists
|
26
|
+
include References
|
27
|
+
include Indices
|
28
|
+
include Logger
|
29
|
+
|
30
|
+
include IdentityMap
|
31
|
+
include Caching
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
extend Connection
|
36
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Toy
|
2
|
+
module Timestamps
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def timestamps
|
7
|
+
attribute(:created_at, Time)
|
8
|
+
attribute(:updated_at, Time)
|
9
|
+
|
10
|
+
before_create do |record|
|
11
|
+
now = Time.now
|
12
|
+
record.created_at = now unless created_at?
|
13
|
+
record.updated_at = now
|
14
|
+
end
|
15
|
+
|
16
|
+
before_update do |record|
|
17
|
+
record.updated_at = Time.now
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Toy
|
2
|
+
module Validations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Validations
|
5
|
+
|
6
|
+
included do
|
7
|
+
extend ActiveModel::Callbacks
|
8
|
+
define_model_callbacks :validation
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def validates_embedded(*names)
|
13
|
+
validates_each(*names) do |record, name, value|
|
14
|
+
invalid = value.compact.select { |o| !o.valid? }
|
15
|
+
if invalid.any?
|
16
|
+
record.errors.add(name, 'is invalid')
|
17
|
+
logger.debug("ToyStore IEM")
|
18
|
+
invalid.each do |o|
|
19
|
+
logger.debug(" #{o.attributes.inspect} - #{o.errors.full_messages.inspect}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def create!(attrs={})
|
26
|
+
new(attrs).tap { |doc| doc.save! }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module InstanceMethods
|
31
|
+
def valid?
|
32
|
+
run_callbacks(:validation) { super }
|
33
|
+
end
|
34
|
+
|
35
|
+
def save(options={})
|
36
|
+
options.assert_valid_keys(:validate)
|
37
|
+
!options.fetch(:validate, true) || valid? ? super : false
|
38
|
+
end
|
39
|
+
|
40
|
+
def save!
|
41
|
+
save || raise(RecordInvalidError.new(self))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|