toystore 0.8.3 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -2
- data/Changelog.md +9 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +71 -0
- data/Guardfile +15 -0
- data/README.md +28 -0
- data/examples/attributes_abbreviation.rb +1 -2
- data/examples/attributes_virtual.rb +1 -2
- data/examples/identity_map.rb +7 -12
- data/examples/memcached.rb +1 -1
- data/examples/memory.rb +1 -1
- data/examples/mongo.rb +1 -1
- data/examples/redis.rb +1 -1
- data/examples/riak.rb +1 -1
- data/lib/toy.rb +40 -39
- data/lib/toy/attribute.rb +1 -6
- data/lib/toy/attributes.rb +61 -90
- data/lib/toy/caching.rb +11 -13
- data/lib/toy/callbacks.rb +12 -31
- data/lib/toy/cloneable.rb +20 -0
- data/lib/toy/collection.rb +8 -7
- data/lib/toy/dirty.rb +17 -36
- data/lib/toy/dirty_store.rb +32 -0
- data/lib/toy/equality.rb +2 -0
- data/lib/toy/extensions/boolean.rb +22 -18
- data/lib/toy/identity_map.rb +39 -62
- data/lib/toy/list.rb +23 -22
- data/lib/toy/logger.rb +2 -17
- data/lib/toy/mass_assignment_security.rb +3 -5
- data/lib/toy/middleware/identity_map.rb +23 -4
- data/lib/toy/object.rb +16 -0
- data/lib/toy/persistence.rb +72 -62
- data/lib/toy/proxies/list.rb +19 -18
- data/lib/toy/proxies/proxy.rb +7 -6
- data/lib/toy/querying.rb +2 -4
- data/lib/toy/reference.rb +28 -26
- data/lib/toy/reloadable.rb +17 -0
- data/lib/toy/serialization.rb +25 -25
- data/lib/toy/store.rb +3 -11
- data/lib/toy/validations.rb +9 -28
- data/lib/toy/version.rb +1 -1
- data/perf/reads.rb +7 -9
- data/perf/writes.rb +6 -8
- data/spec/helper.rb +3 -1
- data/spec/support/constants.rb +1 -4
- data/spec/support/identity_map_matcher.rb +5 -5
- data/spec/support/objects.rb +38 -0
- data/spec/toy/attribute_spec.rb +1 -1
- data/spec/toy/attributes_spec.rb +1 -153
- data/spec/toy/callbacks_spec.rb +1 -45
- data/spec/toy/cloneable_spec.rb +47 -0
- data/spec/toy/dirty_spec.rb +12 -44
- data/spec/toy/dirty_store_spec.rb +47 -0
- data/spec/toy/equality_spec.rb +5 -19
- data/spec/toy/extensions/boolean_spec.rb +2 -0
- data/spec/toy/identity/uuid_key_factory_spec.rb +2 -2
- data/spec/toy/identity_map_spec.rb +45 -37
- data/spec/toy/identity_spec.rb +1 -1
- data/spec/toy/inspect_spec.rb +1 -1
- data/spec/toy/lists_spec.rb +20 -5
- data/spec/toy/logger_spec.rb +1 -29
- data/spec/toy/mass_assignment_security_spec.rb +16 -5
- data/spec/toy/middleware/identity_map_spec.rb +68 -2
- data/spec/toy/persistence_spec.rb +88 -30
- data/spec/toy/reference_spec.rb +0 -1
- data/spec/toy/references_spec.rb +20 -0
- data/spec/toy/reloadable_spec.rb +81 -0
- data/spec/toy/serialization_spec.rb +1 -110
- data/spec/toy/validations_spec.rb +0 -21
- data/spec/toy_spec.rb +4 -5
- data/test/lint_test.rb +1 -1
- metadata +21 -26
- data/.autotest +0 -11
- data/LOGGING.rdoc +0 -12
- data/README.rdoc +0 -27
- data/examples/models.rb +0 -51
- data/lib/toy/dolly.rb +0 -30
- data/lib/toy/embedded_list.rb +0 -45
- data/lib/toy/embedded_lists.rb +0 -68
- data/lib/toy/index.rb +0 -74
- data/lib/toy/indices.rb +0 -56
- data/lib/toy/proxies/embedded_list.rb +0 -79
- data/spec/toy/dolly_spec.rb +0 -76
- data/spec/toy/embedded_list_spec.rb +0 -607
- data/spec/toy/embedded_lists_spec.rb +0 -172
- data/spec/toy/index_spec.rb +0 -230
- data/spec/toy/indices_spec.rb +0 -141
- data/specs.watchr +0 -52
data/LOGGING.rdoc
DELETED
@@ -1,12 +0,0 @@
|
|
1
|
-
What the 3 letter codes mean when they show up in logging:
|
2
|
-
|
3
|
-
SET: write to store
|
4
|
-
GET: read from store
|
5
|
-
DEL: delete from store
|
6
|
-
KEY: check if key exists in store
|
7
|
-
|
8
|
-
IMG: read from identity map
|
9
|
-
IMS: write to identity map
|
10
|
-
IMD: delete from identity map
|
11
|
-
|
12
|
-
IEM: invalid embedded document
|
data/README.rdoc
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
= Toystore
|
2
|
-
|
3
|
-
An object mapper for anything that can read, write and delete data.
|
4
|
-
|
5
|
-
See examples/ for potential direction. The idea is that any key-value store (via adapters) that supports read, write, delete will work (memcache, membase, mongo, redis, couch, toyko. Potentially even RESTFUL services or sqlite with a single key-value table?)
|
6
|
-
|
7
|
-
== Mailing List
|
8
|
-
|
9
|
-
https://groups.google.com/forum/#!forum/toystoreadapter
|
10
|
-
|
11
|
-
== Identity Map
|
12
|
-
|
13
|
-
By default, Toystore has identity map turned on. It assumes that any Toystore model has a unique id across all models. This means you either need to use the default uuid id's or create your own key factory that namespaces to model (see examples).
|
14
|
-
|
15
|
-
You also need to clear the map before each request. For this, there is a provided piece of middleware that you can use.
|
16
|
-
|
17
|
-
use(Toy::Middleware::IdentityMap)
|
18
|
-
|
19
|
-
It is autoloaded, so just add it to your config.ru or sinatra/rails app as you would any other middleware and you are good to go.
|
20
|
-
|
21
|
-
== Note on Patches/Pull Requests
|
22
|
-
|
23
|
-
* Fork the project.
|
24
|
-
* Make your feature addition or bug fix.
|
25
|
-
* Add tests for it. This is important so we don't break it in a future version unintentionally.
|
26
|
-
* Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine, but bump version in a commit by itself so we can ignore when we pull)
|
27
|
-
* Send us a pull request. Bonus points for topic branches.
|
data/examples/models.rb
DELETED
@@ -1,51 +0,0 @@
|
|
1
|
-
require 'pathname'
|
2
|
-
|
3
|
-
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
4
|
-
lib_path = root_path.join('lib')
|
5
|
-
$:.unshift(lib_path)
|
6
|
-
|
7
|
-
require 'toy'
|
8
|
-
require 'adapter/memory'
|
9
|
-
|
10
|
-
class Address
|
11
|
-
include Toy::Store
|
12
|
-
store :memory, {}
|
13
|
-
|
14
|
-
attribute :city, String
|
15
|
-
attribute :state, String
|
16
|
-
attribute :zip, String
|
17
|
-
|
18
|
-
index :zip
|
19
|
-
end
|
20
|
-
|
21
|
-
class PhoneNumber
|
22
|
-
include Toy::Store
|
23
|
-
|
24
|
-
attribute :area_code, String
|
25
|
-
attribute :number, String
|
26
|
-
end
|
27
|
-
|
28
|
-
class Company
|
29
|
-
include Toy::Store
|
30
|
-
store :memory, {}
|
31
|
-
|
32
|
-
attribute :name, String
|
33
|
-
end
|
34
|
-
|
35
|
-
class User
|
36
|
-
include Toy::Store
|
37
|
-
store :memory, {}
|
38
|
-
|
39
|
-
attribute :name, String
|
40
|
-
attribute :age, Integer
|
41
|
-
attribute :admin, Boolean, :default => false
|
42
|
-
attribute :ssn, String
|
43
|
-
timestamps
|
44
|
-
|
45
|
-
index :ssn
|
46
|
-
|
47
|
-
list :addresses, :dependendent => true
|
48
|
-
reference :employer, Company
|
49
|
-
|
50
|
-
# validations and callbacks are available too
|
51
|
-
end
|
data/lib/toy/dolly.rb
DELETED
@@ -1,30 +0,0 @@
|
|
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 = {}
|
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
|
data/lib/toy/embedded_list.rb
DELETED
@@ -1,45 +0,0 @@
|
|
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
|
data/lib/toy/embedded_lists.rb
DELETED
@@ -1,68 +0,0 @@
|
|
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/index.rb
DELETED
@@ -1,74 +0,0 @@
|
|
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
|
data/lib/toy/indices.rb
DELETED
@@ -1,56 +0,0 @@
|
|
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
|
@@ -1,79 +0,0 @@
|
|
1
|
-
require 'toy/proxies/proxy'
|
2
|
-
|
3
|
-
module Toy
|
4
|
-
module Proxies
|
5
|
-
class EmbeddedList < Toy::Proxies::Proxy
|
6
|
-
def get(id)
|
7
|
-
target.detect { |record| record.id == id }
|
8
|
-
end
|
9
|
-
|
10
|
-
def get!(id)
|
11
|
-
get(id) || raise(Toy::NotFound.new(id))
|
12
|
-
end
|
13
|
-
|
14
|
-
def include?(record)
|
15
|
-
return false if record.nil?
|
16
|
-
target.include?(record)
|
17
|
-
end
|
18
|
-
|
19
|
-
def push(instance)
|
20
|
-
assert_type(instance)
|
21
|
-
assign_reference(instance)
|
22
|
-
self.target.push(instance)
|
23
|
-
end
|
24
|
-
alias :<< :push
|
25
|
-
|
26
|
-
def concat(*instances)
|
27
|
-
instances = instances.flatten
|
28
|
-
instances.each do |instance|
|
29
|
-
assert_type(instance)
|
30
|
-
assign_reference(instance)
|
31
|
-
end
|
32
|
-
self.target.concat instances
|
33
|
-
end
|
34
|
-
|
35
|
-
def replace(instances)
|
36
|
-
@target = instances.map do |instance|
|
37
|
-
instance = if instance.is_a?(proxy_class)
|
38
|
-
instance
|
39
|
-
else
|
40
|
-
key = instance.delete('id')
|
41
|
-
proxy_class.load(key, instance)
|
42
|
-
end
|
43
|
-
assign_reference(instance)
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def create(attrs={})
|
48
|
-
proxy_class.new(attrs).tap do |instance|
|
49
|
-
assign_reference(instance)
|
50
|
-
if instance.valid?
|
51
|
-
push(instance)
|
52
|
-
proxy_owner.save
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
def destroy(*args, &block)
|
58
|
-
ids = block_given? ? target.select(&block).map(&:id) : args.flatten
|
59
|
-
target.delete_if { |instance| ids.include?(instance.id) }
|
60
|
-
proxy_owner.save
|
61
|
-
end
|
62
|
-
|
63
|
-
def destroy_all
|
64
|
-
target.clear
|
65
|
-
proxy_owner.save
|
66
|
-
end
|
67
|
-
|
68
|
-
private
|
69
|
-
def find_target
|
70
|
-
[]
|
71
|
-
end
|
72
|
-
|
73
|
-
def assign_reference(instance)
|
74
|
-
instance.parent_reference = proxy_owner
|
75
|
-
instance
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|