wayne-friendly 0.5.1
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/.document +2 -0
- data/.gitignore +26 -0
- data/APACHE-LICENSE +202 -0
- data/CHANGELOG.md +28 -0
- data/CONTRIBUTORS.md +7 -0
- data/LICENSE +20 -0
- data/README.md +288 -0
- data/Rakefile +68 -0
- data/TODO.md +5 -0
- data/VERSION +1 -0
- data/examples/friendly.yml +7 -0
- data/friendly.gemspec +240 -0
- data/lib/friendly.rb +58 -0
- data/lib/friendly/associations.rb +7 -0
- data/lib/friendly/associations/association.rb +34 -0
- data/lib/friendly/associations/set.rb +37 -0
- data/lib/friendly/attribute.rb +98 -0
- data/lib/friendly/boolean.rb +10 -0
- data/lib/friendly/cache.rb +24 -0
- data/lib/friendly/cache/by_id.rb +33 -0
- data/lib/friendly/data_store.rb +73 -0
- data/lib/friendly/document.rb +70 -0
- data/lib/friendly/document/associations.rb +50 -0
- data/lib/friendly/document/attributes.rb +114 -0
- data/lib/friendly/document/convenience.rb +41 -0
- data/lib/friendly/document/mixin.rb +15 -0
- data/lib/friendly/document/scoping.rb +66 -0
- data/lib/friendly/document/storage.rb +63 -0
- data/lib/friendly/document_table.rb +56 -0
- data/lib/friendly/index.rb +73 -0
- data/lib/friendly/indexer.rb +50 -0
- data/lib/friendly/memcached.rb +48 -0
- data/lib/friendly/newrelic.rb +6 -0
- data/lib/friendly/query.rb +42 -0
- data/lib/friendly/scope.rb +100 -0
- data/lib/friendly/scope_proxy.rb +43 -0
- data/lib/friendly/sequel_monkey_patches.rb +34 -0
- data/lib/friendly/storage.rb +31 -0
- data/lib/friendly/storage_factory.rb +24 -0
- data/lib/friendly/storage_proxy.rb +111 -0
- data/lib/friendly/table.rb +15 -0
- data/lib/friendly/table_creator.rb +50 -0
- data/lib/friendly/time.rb +14 -0
- data/lib/friendly/translator.rb +33 -0
- data/lib/friendly/uuid.rb +148 -0
- data/lib/tasks/friendly.rake +7 -0
- data/rails/init.rb +3 -0
- data/spec/config.yml.example +7 -0
- data/spec/fakes/data_store_fake.rb +29 -0
- data/spec/fakes/database_fake.rb +12 -0
- data/spec/fakes/dataset_fake.rb +28 -0
- data/spec/fakes/document.rb +18 -0
- data/spec/fakes/serializer_fake.rb +12 -0
- data/spec/fakes/time_fake.rb +12 -0
- data/spec/integration/ad_hoc_scopes_spec.rb +42 -0
- data/spec/integration/basic_object_lifecycle_spec.rb +114 -0
- data/spec/integration/batch_insertion_spec.rb +29 -0
- data/spec/integration/convenience_api_spec.rb +25 -0
- data/spec/integration/count_spec.rb +12 -0
- data/spec/integration/default_value_spec.rb +30 -0
- data/spec/integration/dirty_tracking_spec.rb +43 -0
- data/spec/integration/find_via_cache_spec.rb +101 -0
- data/spec/integration/finder_spec.rb +71 -0
- data/spec/integration/has_many_spec.rb +18 -0
- data/spec/integration/index_spec.rb +57 -0
- data/spec/integration/named_scope_spec.rb +34 -0
- data/spec/integration/offline_indexing_spec.rb +53 -0
- data/spec/integration/pagination_spec.rb +63 -0
- data/spec/integration/scope_chaining_spec.rb +22 -0
- data/spec/integration/table_creator_spec.rb +69 -0
- data/spec/integration/write_through_cache_spec.rb +53 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +105 -0
- data/spec/unit/associations/association_spec.rb +57 -0
- data/spec/unit/associations/set_spec.rb +43 -0
- data/spec/unit/attribute_spec.rb +125 -0
- data/spec/unit/cache_by_id_spec.rb +102 -0
- data/spec/unit/cache_spec.rb +21 -0
- data/spec/unit/data_store_spec.rb +201 -0
- data/spec/unit/document/attributes_spec.rb +130 -0
- data/spec/unit/document_spec.rb +318 -0
- data/spec/unit/document_table_spec.rb +126 -0
- data/spec/unit/friendly_spec.rb +25 -0
- data/spec/unit/index_spec.rb +196 -0
- data/spec/unit/memcached_spec.rb +114 -0
- data/spec/unit/query_spec.rb +104 -0
- data/spec/unit/scope_proxy_spec.rb +44 -0
- data/spec/unit/scope_spec.rb +113 -0
- data/spec/unit/storage_factory_spec.rb +59 -0
- data/spec/unit/storage_proxy_spec.rb +244 -0
- data/spec/unit/translator_spec.rb +91 -0
- data/website/index.html +210 -0
- data/website/scripts/clipboard.swf +0 -0
- data/website/scripts/shBrushAS3.js +61 -0
- data/website/scripts/shBrushBash.js +66 -0
- data/website/scripts/shBrushCSharp.js +67 -0
- data/website/scripts/shBrushColdFusion.js +102 -0
- data/website/scripts/shBrushCpp.js +99 -0
- data/website/scripts/shBrushCss.js +93 -0
- data/website/scripts/shBrushDelphi.js +57 -0
- data/website/scripts/shBrushDiff.js +43 -0
- data/website/scripts/shBrushErlang.js +54 -0
- data/website/scripts/shBrushGroovy.js +69 -0
- data/website/scripts/shBrushJScript.js +52 -0
- data/website/scripts/shBrushJava.js +59 -0
- data/website/scripts/shBrushJavaFX.js +60 -0
- data/website/scripts/shBrushPerl.js +74 -0
- data/website/scripts/shBrushPhp.js +91 -0
- data/website/scripts/shBrushPlain.js +35 -0
- data/website/scripts/shBrushPowerShell.js +76 -0
- data/website/scripts/shBrushPython.js +66 -0
- data/website/scripts/shBrushRuby.js +57 -0
- data/website/scripts/shBrushScala.js +53 -0
- data/website/scripts/shBrushSql.js +68 -0
- data/website/scripts/shBrushVb.js +58 -0
- data/website/scripts/shBrushXml.js +71 -0
- data/website/scripts/shCore.js +30 -0
- data/website/scripts/shLegacy.js +30 -0
- data/website/styles/friendly.css +103 -0
- data/website/styles/help.png +0 -0
- data/website/styles/ie.css +35 -0
- data/website/styles/magnifier.png +0 -0
- data/website/styles/page_white_code.png +0 -0
- data/website/styles/page_white_copy.png +0 -0
- data/website/styles/print.css +29 -0
- data/website/styles/printer.png +0 -0
- data/website/styles/screen.css +257 -0
- data/website/styles/shCore.css +330 -0
- data/website/styles/shThemeDefault.css +173 -0
- data/website/styles/shThemeDjango.css +176 -0
- data/website/styles/shThemeEclipse.css +190 -0
- data/website/styles/shThemeEmacs.css +175 -0
- data/website/styles/shThemeFadeToGrey.css +177 -0
- data/website/styles/shThemeMidnight.css +175 -0
- data/website/styles/shThemeRDark.css +175 -0
- metadata +337 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class Scope
|
|
3
|
+
attr_reader :klass, :parameters
|
|
4
|
+
|
|
5
|
+
def initialize(klass, parameters)
|
|
6
|
+
@klass = klass
|
|
7
|
+
@parameters = parameters
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# Fetch all documents at this scope.
|
|
11
|
+
#
|
|
12
|
+
# @param [Hash] extra_parameters add extra parameters to this query.
|
|
13
|
+
#
|
|
14
|
+
def all(extra_parameters = {})
|
|
15
|
+
klass.all(params(extra_parameters))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Fetch the first document at this scope.
|
|
19
|
+
#
|
|
20
|
+
# @param [Hash] extra_parameters add extra parameters to this query.
|
|
21
|
+
#
|
|
22
|
+
def first(extra_parameters = {})
|
|
23
|
+
klass.first(params(extra_parameters))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Paginate the documents at this scope.
|
|
27
|
+
#
|
|
28
|
+
# @param [Hash] extra_parameters add extra parameters to this query.
|
|
29
|
+
# @return WillPaginate::Collection
|
|
30
|
+
#
|
|
31
|
+
def paginate(extra_parameters = {})
|
|
32
|
+
klass.paginate(params(extra_parameters))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Build an object at this scope.
|
|
36
|
+
#
|
|
37
|
+
# e.g.
|
|
38
|
+
# Post.scope(:name => "James").build.name # => "James"
|
|
39
|
+
#
|
|
40
|
+
# @param [Hash] extra_parameters add extra parameters to this query.
|
|
41
|
+
#
|
|
42
|
+
def build(extra_parameters = {})
|
|
43
|
+
klass.new(params_without_modifiers(extra_parameters))
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Create an object at this scope.
|
|
47
|
+
#
|
|
48
|
+
# e.g.
|
|
49
|
+
# @post = Post.scope(:name => "James").create
|
|
50
|
+
# @post.new_record? # => false
|
|
51
|
+
# @post.name # => "James"
|
|
52
|
+
#
|
|
53
|
+
# @param [Hash] extra_parameters add extra parameters to this query.
|
|
54
|
+
#
|
|
55
|
+
def create(extra_parameters = {})
|
|
56
|
+
klass.create(params_without_modifiers(extra_parameters))
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Override #respond_to? so that we can return true when it's another named_scope.
|
|
60
|
+
#
|
|
61
|
+
# @override
|
|
62
|
+
#
|
|
63
|
+
def respond_to?(method_name, include_private = false)
|
|
64
|
+
klass.has_named_scope?(method_name) || super
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Use method_missing to respond to other named scopes on klass.
|
|
68
|
+
#
|
|
69
|
+
# @override
|
|
70
|
+
#
|
|
71
|
+
def method_missing(method_name, *args, &block)
|
|
72
|
+
respond_to?(method_name) ? chain_with(method_name) : super
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Chain with another one of klass's named_scopes.
|
|
76
|
+
#
|
|
77
|
+
# @param [Symbol] scope_name The name of the scope to chain with.
|
|
78
|
+
#
|
|
79
|
+
def chain_with(scope_name)
|
|
80
|
+
self + klass.send(scope_name)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Create a new Scope that is the combination of self and other, where other takes priority
|
|
84
|
+
#
|
|
85
|
+
# @param [Friendly::Scope] other The scope to merge with.
|
|
86
|
+
#
|
|
87
|
+
def +(other_scope)
|
|
88
|
+
self.class.new(klass, parameters.merge(other_scope.parameters))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
protected
|
|
92
|
+
def params(extra)
|
|
93
|
+
parameters.merge(extra)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def params_without_modifiers(extra)
|
|
97
|
+
params(extra).reject { |k,v| k.to_s =~ /!$/ }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class ScopeProxy
|
|
3
|
+
attr_reader :klass, :scope_klass, :scopes
|
|
4
|
+
|
|
5
|
+
def initialize(klass, scope_klass = Scope)
|
|
6
|
+
@klass = klass
|
|
7
|
+
@scope_klass = scope_klass
|
|
8
|
+
@scopes = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_named(name, parameters)
|
|
12
|
+
scopes[name] = parameters
|
|
13
|
+
add_scope_method_to_klass(name)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(name)
|
|
17
|
+
scopes[name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get_instance(name)
|
|
21
|
+
scope_klass.new(klass, get(name))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def ad_hoc(parameters)
|
|
25
|
+
scope_klass.new(klass, parameters)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def has_named_scope?(name)
|
|
29
|
+
scopes.has_key?(name)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
protected
|
|
33
|
+
def add_scope_method_to_klass(scope_name)
|
|
34
|
+
klass.class_eval do
|
|
35
|
+
eval <<-__END__
|
|
36
|
+
def self.#{scope_name}
|
|
37
|
+
scope_proxy.get_instance(:#{scope_name})
|
|
38
|
+
end
|
|
39
|
+
__END__
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'sequel'
|
|
2
|
+
|
|
3
|
+
# Out of the box, Sequel uses IS TRUE/FALSE for boolean parameters
|
|
4
|
+
# This prevents MySQL from using indexes.
|
|
5
|
+
#
|
|
6
|
+
# This patch fixes that.
|
|
7
|
+
module Sequel
|
|
8
|
+
module SQL
|
|
9
|
+
class BooleanExpression
|
|
10
|
+
def self.from_value_pairs(pairs, op=:AND, negate=false)
|
|
11
|
+
pairs = pairs.collect do |l,r|
|
|
12
|
+
ce = case r
|
|
13
|
+
when Range
|
|
14
|
+
new(:AND, new(:>=, l, r.begin), new(r.exclude_end? ? :< : :<=, l, r.end))
|
|
15
|
+
when Array, ::Sequel::Dataset, SQLArray
|
|
16
|
+
new(:IN, l, r)
|
|
17
|
+
when NegativeBooleanConstant
|
|
18
|
+
new(:"IS NOT", l, r.constant)
|
|
19
|
+
when BooleanConstant
|
|
20
|
+
new(:IS, l, r.constant)
|
|
21
|
+
when NilClass
|
|
22
|
+
new(:IS, l, r)
|
|
23
|
+
when Regexp
|
|
24
|
+
StringExpression.like(l, r)
|
|
25
|
+
else
|
|
26
|
+
new(:'=', l, r)
|
|
27
|
+
end
|
|
28
|
+
negate ? invert(ce) : ce
|
|
29
|
+
end
|
|
30
|
+
pairs.length == 1 ? pairs.at(0) : new(op, *pairs)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class Storage
|
|
3
|
+
def create(document)
|
|
4
|
+
raise NotImplementedError, "#{self.class.name}#create is not implemented."
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def update(document)
|
|
8
|
+
raise NotImplementedError, "#{self.class.name}#update is not implemented."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def destroy(document)
|
|
12
|
+
raise NotImplementedError, "#{self.class.name}#destroy is not implemented."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def first(conditions)
|
|
16
|
+
raise NotImplementedError, "#{self.class.name}#first is not implemented."
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def all(conditions)
|
|
20
|
+
raise NotImplementedError, "#{self.class.name}#all is not implemented."
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def count(query)
|
|
24
|
+
raise NotImplementedError, "#{self.class.name}#count is not implemented."
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def satisfies?(conditions)
|
|
28
|
+
raise NotImplementedError, "#{self.class.name}#satisfies? is not implemented."
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class StorageFactory
|
|
3
|
+
attr_reader :table_klass, :index_klass, :cache_klass
|
|
4
|
+
|
|
5
|
+
def initialize(table_klass = DocumentTable, index_klass = Index,
|
|
6
|
+
cache_klass = Cache)
|
|
7
|
+
@table_klass = table_klass
|
|
8
|
+
@index_klass = index_klass
|
|
9
|
+
@cache_klass = cache_klass
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def document_table(*args)
|
|
13
|
+
table_klass.new(*args)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def index(*args)
|
|
17
|
+
index_klass.new(*args)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def cache(*args)
|
|
21
|
+
cache_klass.cache_for(*args)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
require 'friendly/storage_factory'
|
|
2
|
+
require 'friendly/table_creator'
|
|
3
|
+
|
|
4
|
+
module Friendly
|
|
5
|
+
class StorageProxy
|
|
6
|
+
attr_reader :klass, :storage_factory, :tables, :table_creator, :caches
|
|
7
|
+
|
|
8
|
+
def initialize(klass, storage_factory = StorageFactory.new,
|
|
9
|
+
table_creator=TableCreator.new)
|
|
10
|
+
super()
|
|
11
|
+
@klass = klass
|
|
12
|
+
@storage_factory = storage_factory
|
|
13
|
+
@table_creator = table_creator
|
|
14
|
+
@tables = [storage_factory.document_table(klass)]
|
|
15
|
+
@caches = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def first(conditions)
|
|
19
|
+
first_from_cache(conditions) do
|
|
20
|
+
index_for(conditions).first(conditions)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def all(query)
|
|
25
|
+
objects = perform_all(query).compact
|
|
26
|
+
if query.preserve_order?
|
|
27
|
+
order = query.conditions[:id]
|
|
28
|
+
objects.sort { |a,b| order.index(a.id) <=> order.index(b.id) }
|
|
29
|
+
else
|
|
30
|
+
objects
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def count(query)
|
|
35
|
+
index_for(query).count(query)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add(*args)
|
|
39
|
+
tables << storage_factory.index(klass, *args)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cache(fields, options = {})
|
|
43
|
+
caches << storage_factory.cache(klass, fields, options)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create(document)
|
|
47
|
+
each_store { |s| s.create(document) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def update(document)
|
|
51
|
+
each_store { |s| s.update(document) }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def destroy(document)
|
|
55
|
+
stores.reverse.each { |i| i.destroy(document) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def create_tables!
|
|
59
|
+
tables.each { |t| table_creator.create(t) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def index_for(conditions)
|
|
63
|
+
index = tables.detect { |i| i.satisfies?(conditions) }
|
|
64
|
+
if index.nil?
|
|
65
|
+
raise MissingIndex, "No index found to satisfy: #{conditions.inspect}."
|
|
66
|
+
end
|
|
67
|
+
index
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def index_for_fields(fields)
|
|
71
|
+
tables.detect do |t|
|
|
72
|
+
t.respond_to?(:fields) && t.fields == fields
|
|
73
|
+
end.tap do |i|
|
|
74
|
+
raise MissingIndex, "No index found matching #{fields.join(", ")}." if i.nil?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
protected
|
|
79
|
+
def each_store
|
|
80
|
+
stores.each { |s| yield(s) }
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def stores
|
|
84
|
+
tables + caches
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def first_from_cache(query)
|
|
88
|
+
cache = cache_for(query)
|
|
89
|
+
if cache
|
|
90
|
+
cache.first(query) { yield }
|
|
91
|
+
else
|
|
92
|
+
yield
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def cache_for(query)
|
|
97
|
+
caches.detect { |c| c.satisfies?(query) }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def perform_all(query)
|
|
101
|
+
cache = cache_for(query)
|
|
102
|
+
if cache
|
|
103
|
+
cache.all(query) do |missing_key|
|
|
104
|
+
index_for(query).first(Query.new(:id => missing_key.split("/").last))
|
|
105
|
+
end
|
|
106
|
+
else
|
|
107
|
+
index_for(query).all(query)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
require 'friendly/storage'
|
|
2
|
+
module Friendly
|
|
3
|
+
class Table < Storage
|
|
4
|
+
attr_reader :datastore
|
|
5
|
+
|
|
6
|
+
def initialize(datastore)
|
|
7
|
+
@datastore = datastore
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def table_name
|
|
11
|
+
raise NotImplementedError, "#{self.class.name}#table_name is not implemented."
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class TableCreator
|
|
3
|
+
attr_reader :db, :attr_klass
|
|
4
|
+
|
|
5
|
+
def initialize(db = Friendly.db, attr_klass = Friendly::Attribute)
|
|
6
|
+
@db = db
|
|
7
|
+
@attr_klass = attr_klass
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def create(table)
|
|
11
|
+
unless db.table_exists?(table.table_name)
|
|
12
|
+
case table
|
|
13
|
+
when DocumentTable
|
|
14
|
+
create_document_table(table)
|
|
15
|
+
when Index
|
|
16
|
+
create_index_table(table)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
protected
|
|
22
|
+
def create_document_table(table)
|
|
23
|
+
db.create_table(table.table_name) do
|
|
24
|
+
primary_key :added_id
|
|
25
|
+
binary :id, :size => 16
|
|
26
|
+
String :attributes, :text => true
|
|
27
|
+
Time :created_at
|
|
28
|
+
Time :updated_at
|
|
29
|
+
|
|
30
|
+
unique :id
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_index_table(table)
|
|
35
|
+
attr = attr_klass # close around this please
|
|
36
|
+
|
|
37
|
+
db.create_table(table.table_name) do
|
|
38
|
+
binary :id, :size => 16
|
|
39
|
+
table.fields.flatten.each do |f|
|
|
40
|
+
klass = table.klass.attributes[f].type
|
|
41
|
+
type = attr.custom_type?(klass) ? attr.sql_type(klass) : klass
|
|
42
|
+
column(f, type)
|
|
43
|
+
end
|
|
44
|
+
primary_key table.fields.flatten + [:id]
|
|
45
|
+
unique :id
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# This class was extracted from the cassandra gem by Evan Weaver
|
|
2
|
+
# As such, it is distributed under the terms of the apache license.
|
|
3
|
+
# See the APACHE-LICENSE file in the root of this project for more information.
|
|
4
|
+
#
|
|
5
|
+
class Time
|
|
6
|
+
def self.stamp
|
|
7
|
+
Time.now.stamp
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def stamp
|
|
11
|
+
to_i * 1_000_000 + usec
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Friendly
|
|
2
|
+
class Translator
|
|
3
|
+
RESERVED_ATTRS = [:id, :created_at, :updated_at].freeze
|
|
4
|
+
|
|
5
|
+
attr_reader :serializer, :time
|
|
6
|
+
|
|
7
|
+
def initialize(serializer = JSON, time = Time)
|
|
8
|
+
@serializer = serializer
|
|
9
|
+
@time = time
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def to_object(klass, record)
|
|
13
|
+
record.delete(:added_id)
|
|
14
|
+
attributes = serializer.parse(record.delete(:attributes))
|
|
15
|
+
attributes.merge!(record).merge!(:new_record => false)
|
|
16
|
+
klass.new_without_change_tracking attributes
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_record(document)
|
|
20
|
+
{ :id => document.id,
|
|
21
|
+
:created_at => document.created_at,
|
|
22
|
+
:updated_at => time.new,
|
|
23
|
+
:attributes => serialize(document) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
protected
|
|
27
|
+
def serialize(document)
|
|
28
|
+
attrs = document.to_hash.reject { |k,v| RESERVED_ATTRS.include?(k) }
|
|
29
|
+
serializer.generate(attrs)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|