wyngle-ripple 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +35 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +133 -0
- data/Gemfile.rails30 +3 -0
- data/Gemfile.rails31 +3 -0
- data/Gemfile.rails32 +3 -0
- data/Guardfile +17 -0
- data/LICENSE +16 -0
- data/README.markdown +166 -0
- data/RELEASE_NOTES.textile +286 -0
- data/Rakefile +63 -0
- data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
- data/lib/rails/generators/ripple/configuration/templates/ripple.yml +25 -0
- data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
- data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
- data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
- data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
- data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
- data/lib/rails/generators/ripple/model/templates/model.rb.erb +10 -0
- data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
- data/lib/rails/generators/ripple/observer/templates/observer.rb.erb +2 -0
- data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
- data/lib/rails/generators/ripple/test/test_generator.rb +45 -0
- data/lib/rails/generators/ripple_generator.rb +79 -0
- data/lib/ripple.rb +86 -0
- data/lib/ripple/associations.rb +380 -0
- data/lib/ripple/associations/embedded.rb +35 -0
- data/lib/ripple/associations/instantiators.rb +26 -0
- data/lib/ripple/associations/linked.rb +65 -0
- data/lib/ripple/associations/many.rb +38 -0
- data/lib/ripple/associations/many_embedded_proxy.rb +39 -0
- data/lib/ripple/associations/many_linked_proxy.rb +66 -0
- data/lib/ripple/associations/many_reference_proxy.rb +95 -0
- data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
- data/lib/ripple/associations/one.rb +20 -0
- data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
- data/lib/ripple/associations/one_key_proxy.rb +58 -0
- data/lib/ripple/associations/one_linked_proxy.rb +26 -0
- data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
- data/lib/ripple/associations/proxy.rb +118 -0
- data/lib/ripple/attribute_methods.rb +132 -0
- data/lib/ripple/attribute_methods/dirty.rb +59 -0
- data/lib/ripple/attribute_methods/query.rb +34 -0
- data/lib/ripple/attribute_methods/read.rb +28 -0
- data/lib/ripple/attribute_methods/write.rb +25 -0
- data/lib/ripple/callbacks.rb +71 -0
- data/lib/ripple/conflict/basic_resolver.rb +86 -0
- data/lib/ripple/conflict/document_hooks.rb +46 -0
- data/lib/ripple/conflict/resolver.rb +96 -0
- data/lib/ripple/conflict/test_helper.rb +34 -0
- data/lib/ripple/conversion.rb +29 -0
- data/lib/ripple/core_ext.rb +3 -0
- data/lib/ripple/core_ext/casting.rb +151 -0
- data/lib/ripple/core_ext/indexes.rb +89 -0
- data/lib/ripple/core_ext/object.rb +8 -0
- data/lib/ripple/document.rb +105 -0
- data/lib/ripple/document/bucket_access.rb +25 -0
- data/lib/ripple/document/finders.rb +131 -0
- data/lib/ripple/document/key.rb +35 -0
- data/lib/ripple/document/link.rb +30 -0
- data/lib/ripple/document/persistence.rb +130 -0
- data/lib/ripple/embedded_document.rb +63 -0
- data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
- data/lib/ripple/embedded_document/finders.rb +26 -0
- data/lib/ripple/embedded_document/persistence.rb +75 -0
- data/lib/ripple/i18n.rb +5 -0
- data/lib/ripple/indexes.rb +151 -0
- data/lib/ripple/inspection.rb +32 -0
- data/lib/ripple/locale/en.yml +26 -0
- data/lib/ripple/locale/fr.yml +24 -0
- data/lib/ripple/nested_attributes.rb +275 -0
- data/lib/ripple/observable.rb +28 -0
- data/lib/ripple/properties.rb +74 -0
- data/lib/ripple/property_type_mismatch.rb +12 -0
- data/lib/ripple/railtie.rb +26 -0
- data/lib/ripple/railties/ripple.rake +68 -0
- data/lib/ripple/serialization.rb +82 -0
- data/lib/ripple/test_server.rb +35 -0
- data/lib/ripple/timestamps.rb +25 -0
- data/lib/ripple/translation.rb +18 -0
- data/lib/ripple/validations.rb +65 -0
- data/lib/ripple/validations/associated_validator.rb +43 -0
- data/lib/ripple/version.rb +3 -0
- data/ripple.gemspec +29 -0
- data/spec/fixtures/config.yml +8 -0
- data/spec/generators/ripple/configuration_generator_spec.rb +9 -0
- data/spec/generators/ripple/js_generator_spec.rb +14 -0
- data/spec/generators/ripple/model_generator_spec.rb +64 -0
- data/spec/generators/ripple/observer_generator_spec.rb +20 -0
- data/spec/generators/ripple/test_generator_spec.rb +118 -0
- data/spec/generators/ripple_generator_spec.rb +11 -0
- data/spec/integration/ripple/associations_spec.rb +164 -0
- data/spec/integration/ripple/conflict_resolution_spec.rb +329 -0
- data/spec/integration/ripple/indexes_spec.rb +47 -0
- data/spec/integration/ripple/nested_attributes_spec.rb +261 -0
- data/spec/integration/ripple/persistence_spec.rb +36 -0
- data/spec/integration/ripple/search_associations_spec.rb +31 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +119 -0
- data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
- data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
- data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
- data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
- data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
- data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
- data/spec/ripple/associations/proxy_spec.rb +84 -0
- data/spec/ripple/associations_spec.rb +153 -0
- data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
- data/spec/ripple/attribute_methods_spec.rb +286 -0
- data/spec/ripple/bucket_access_spec.rb +25 -0
- data/spec/ripple/callbacks_spec.rb +195 -0
- data/spec/ripple/conflict/resolver_spec.rb +42 -0
- data/spec/ripple/conversion_spec.rb +14 -0
- data/spec/ripple/core_ext_spec.rb +181 -0
- data/spec/ripple/document/link_spec.rb +67 -0
- data/spec/ripple/document_spec.rb +96 -0
- data/spec/ripple/embedded_document/finders_spec.rb +29 -0
- data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
- data/spec/ripple/embedded_document_spec.rb +84 -0
- data/spec/ripple/finders_spec.rb +220 -0
- data/spec/ripple/indexes_spec.rb +111 -0
- data/spec/ripple/inspection_spec.rb +51 -0
- data/spec/ripple/key_spec.rb +31 -0
- data/spec/ripple/observable_spec.rb +120 -0
- data/spec/ripple/persistence_spec.rb +351 -0
- data/spec/ripple/properties_spec.rb +262 -0
- data/spec/ripple/ripple_spec.rb +71 -0
- data/spec/ripple/serialization_spec.rb +51 -0
- data/spec/ripple/timestamps_spec.rb +83 -0
- data/spec/ripple/validations/associated_validator_spec.rb +77 -0
- data/spec/ripple/validations_spec.rb +102 -0
- data/spec/spec_helper.rb +39 -0
- data/spec/support/associations.rb +1 -0
- data/spec/support/associations/proxies.rb +17 -0
- data/spec/support/generator_setup.rb +26 -0
- data/spec/support/integration_setup.rb +11 -0
- data/spec/support/models.rb +35 -0
- data/spec/support/models/address.rb +10 -0
- data/spec/support/models/box.rb +13 -0
- data/spec/support/models/car.rb +41 -0
- data/spec/support/models/cardboard_box.rb +2 -0
- data/spec/support/models/clock.rb +10 -0
- data/spec/support/models/clock_observer.rb +2 -0
- data/spec/support/models/company.rb +23 -0
- data/spec/support/models/credit_card.rb +5 -0
- data/spec/support/models/customer.rb +3 -0
- data/spec/support/models/email.rb +3 -0
- data/spec/support/models/family.rb +16 -0
- data/spec/support/models/favorite.rb +3 -0
- data/spec/support/models/indexer.rb +26 -0
- data/spec/support/models/invoice.rb +6 -0
- data/spec/support/models/late_invoice.rb +2 -0
- data/spec/support/models/nested.rb +12 -0
- data/spec/support/models/ninja.rb +7 -0
- data/spec/support/models/note.rb +4 -0
- data/spec/support/models/page.rb +3 -0
- data/spec/support/models/paid_invoice.rb +3 -0
- data/spec/support/models/post.rb +13 -0
- data/spec/support/models/profile.rb +7 -0
- data/spec/support/models/subscription.rb +26 -0
- data/spec/support/models/tasks.rb +9 -0
- data/spec/support/models/team.rb +11 -0
- data/spec/support/models/transactions.rb +17 -0
- data/spec/support/models/tree.rb +3 -0
- data/spec/support/models/user.rb +19 -0
- data/spec/support/models/widget.rb +23 -0
- data/spec/support/search.rb +14 -0
- data/spec/support/test_server.rb +39 -0
- data/spec/support/test_server.yml.example +14 -0
- metadata +434 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Document
|
5
|
+
module Key
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# Defines the key to be derived from a property.
|
10
|
+
# @param [String,Symbol] prop the property to derive the key from
|
11
|
+
def key_on(prop)
|
12
|
+
prop = prop.to_sym
|
13
|
+
|
14
|
+
define_method(:key) { send(prop).to_s }
|
15
|
+
define_method(:key=) { |v| send(:"#{prop}=", v) }
|
16
|
+
define_method(:key_attr) { prop }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Reads the key for this Document.
|
21
|
+
def key
|
22
|
+
@key
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sets the key for this Document.
|
26
|
+
def key=(value)
|
27
|
+
@key = value.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def key_attr
|
31
|
+
:key
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'riak/link'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Document
|
5
|
+
# A Link that is tied to a particular document and tag.
|
6
|
+
# The key is fetched from the document lazily when needed.
|
7
|
+
class Link < Riak::Link
|
8
|
+
attr_reader :document
|
9
|
+
private :document
|
10
|
+
|
11
|
+
def initialize(document, tag)
|
12
|
+
@document = document
|
13
|
+
super(document.class.bucket_name, nil, tag)
|
14
|
+
end
|
15
|
+
|
16
|
+
def key
|
17
|
+
document.key
|
18
|
+
end
|
19
|
+
|
20
|
+
def hash
|
21
|
+
document.hash
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_link(tag)
|
26
|
+
Link.new(self, tag)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Document
|
5
|
+
module Persistence
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
# Instantiates a new record, applies attributes from a block, and saves it
|
11
|
+
def create(*args, &block)
|
12
|
+
new(*args, &block).tap {|s| s.save }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Destroys all records one at a time.
|
16
|
+
# Place holder while :delete to bucket is being developed.
|
17
|
+
def destroy_all
|
18
|
+
list(&:destroy)
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_writer :quorums
|
22
|
+
alias_method "set_quorums", "quorums="
|
23
|
+
|
24
|
+
def quorums
|
25
|
+
@quorums ||= {}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @private
|
30
|
+
def initialize
|
31
|
+
super
|
32
|
+
@new = true
|
33
|
+
@deleted = false
|
34
|
+
end
|
35
|
+
|
36
|
+
# Determines whether this document has been deleted or not.
|
37
|
+
def deleted?
|
38
|
+
@deleted
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determines whether this is a new document.
|
42
|
+
def new?
|
43
|
+
@new || false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Updates a single attribute and then saves the document
|
47
|
+
# NOTE: THIS SKIPS VALIDATIONS! Use with caution.
|
48
|
+
# @return [true,false] whether the document succeeded in saving
|
49
|
+
def update_attribute(attribute, value)
|
50
|
+
send("#{attribute}=", value)
|
51
|
+
save(:validate => false)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Writes new attributes and then saves the document
|
55
|
+
# @return [true,false] whether the document succeeded in saving
|
56
|
+
def update_attributes(attrs)
|
57
|
+
self.attributes = attrs
|
58
|
+
save
|
59
|
+
end
|
60
|
+
|
61
|
+
# Saves the document in Riak.
|
62
|
+
# @return [true,false] whether the document succeeded in saving
|
63
|
+
def save(*args)
|
64
|
+
really_save(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def really_save(*args)
|
68
|
+
update_robject
|
69
|
+
robject.store(self.class.quorums.slice(:w,:dw))
|
70
|
+
self.key = robject.key
|
71
|
+
@new = false
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
# Reloads the document from Riak
|
76
|
+
# @return self
|
77
|
+
def reload
|
78
|
+
return self if new?
|
79
|
+
@robject = @robject.reload(:force => true)
|
80
|
+
self.__send__(:raw_attributes=, @robject.data.except("_type"))
|
81
|
+
reset_associations
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
# Deletes the document from Riak and freezes this instance
|
86
|
+
def destroy!
|
87
|
+
robject.delete(self.class.quorums.slice(:rw)) unless new?
|
88
|
+
@deleted = true
|
89
|
+
freeze
|
90
|
+
end
|
91
|
+
|
92
|
+
def destroy
|
93
|
+
destroy!
|
94
|
+
true
|
95
|
+
rescue Riak::FailedRequest
|
96
|
+
false
|
97
|
+
end
|
98
|
+
|
99
|
+
# Freeze the attributes hash instead of the record itself to avoid
|
100
|
+
# errors when calling methods on frozen records.
|
101
|
+
def freeze
|
102
|
+
@attributes.freeze
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns +true+ if the attributes hash has been frozen.
|
106
|
+
def frozen?
|
107
|
+
@attributes.frozen?
|
108
|
+
end
|
109
|
+
|
110
|
+
attr_writer :robject
|
111
|
+
|
112
|
+
def robject
|
113
|
+
@robject ||= Riak::RObject.new(self.class.bucket, key).tap do |obj|
|
114
|
+
obj.content_type = "application/json"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def update_robject
|
119
|
+
robject.key = key if robject.key != key
|
120
|
+
robject.content_type = 'application/json'
|
121
|
+
robject.data = attributes_for_persistence
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
def attributes_for_persistence
|
126
|
+
raw_attributes.merge("_type" => self.class.name)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/core_ext/hash/except'
|
3
|
+
require 'ripple/translation'
|
4
|
+
require 'ripple/embedded_document/around_callbacks'
|
5
|
+
require 'ripple/embedded_document/finders'
|
6
|
+
require 'ripple/embedded_document/persistence'
|
7
|
+
require 'ripple/properties'
|
8
|
+
require 'ripple/attribute_methods'
|
9
|
+
require 'ripple/timestamps'
|
10
|
+
require 'ripple/validations'
|
11
|
+
require 'ripple/associations'
|
12
|
+
require 'ripple/callbacks'
|
13
|
+
require 'ripple/conversion'
|
14
|
+
require 'ripple/inspection'
|
15
|
+
require 'ripple/nested_attributes'
|
16
|
+
require 'ripple/serialization'
|
17
|
+
|
18
|
+
module Ripple
|
19
|
+
# Represents a document model that is composed into or stored in a parent
|
20
|
+
# Document. Embedded documents may also embed other documents, have
|
21
|
+
# callbacks and validations, but are solely dependent on the parent Document.
|
22
|
+
module EmbeddedDocument
|
23
|
+
extend ActiveSupport::Concern
|
24
|
+
include Translation
|
25
|
+
|
26
|
+
included do
|
27
|
+
extend ActiveModel::Naming
|
28
|
+
include Persistence
|
29
|
+
extend Ripple::Properties
|
30
|
+
include Ripple::AttributeMethods
|
31
|
+
include Ripple::Indexes
|
32
|
+
include Ripple::Timestamps
|
33
|
+
include Ripple::Validations
|
34
|
+
include Ripple::Associations
|
35
|
+
include Ripple::Callbacks
|
36
|
+
include Ripple::EmbeddedDocument::AroundCallbacks
|
37
|
+
include Ripple::Conversion
|
38
|
+
include Finders
|
39
|
+
include Ripple::Inspection
|
40
|
+
include Ripple::NestedAttributes
|
41
|
+
include Ripple::Serialization
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
def embeddable?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def ==(other)
|
51
|
+
self.class == other.class &&
|
52
|
+
_parent_document == other._parent_document &&
|
53
|
+
serializable_hash == other.serializable_hash
|
54
|
+
end
|
55
|
+
alias eql? ==
|
56
|
+
|
57
|
+
def hash
|
58
|
+
hash = self.class.hash ^ _parent_document.class.hash ^ serializable_hash.to_s.hash
|
59
|
+
hash ^= _parent_document.key.hash if _parent_document.respond_to?(:key)
|
60
|
+
hash
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'ripple/callbacks'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module EmbeddedDocument
|
5
|
+
module AroundCallbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
extend Translation
|
8
|
+
|
9
|
+
included do
|
10
|
+
Ripple::Callbacks::CALLBACK_TYPES.each do |type|
|
11
|
+
define_singleton_method "around_#{type}" do |*args|
|
12
|
+
raise NotImplementedError.new(t("around_callbacks_not_supported", :type => type))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_support/inflector'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
module EmbeddedDocument
|
6
|
+
# @private
|
7
|
+
module Finders
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def instantiate(attrs)
|
12
|
+
begin
|
13
|
+
klass = attrs['_type'].present? ? attrs.delete('_type').constantize : self
|
14
|
+
rescue NameError
|
15
|
+
klass = self
|
16
|
+
end
|
17
|
+
klass.new.tap do |object|
|
18
|
+
object.raw_attributes = attrs
|
19
|
+
object.changed_attributes.clear
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'ripple/translation'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
# Exception raised when save is called on an EmbeddedDocument that
|
6
|
+
# is not attached to a root Document.
|
7
|
+
class NoRootDocument < StandardError
|
8
|
+
include Translation
|
9
|
+
def initialize(doc, method)
|
10
|
+
super(t("no_root_document", :doc => doc.inspect, :method => method))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module EmbeddedDocument
|
15
|
+
# Adds methods to {Ripple::EmbeddedDocument} that delegate storage
|
16
|
+
# operations to the parent document.
|
17
|
+
module Persistence
|
18
|
+
extend ActiveSupport::Concern
|
19
|
+
|
20
|
+
module ClassMethods
|
21
|
+
# Creates a method that points to the parent document.
|
22
|
+
def embedded_in(parent)
|
23
|
+
define_method(parent) { @_parent_document }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# The parent document to this embedded document. This may be a
|
28
|
+
# {Ripple::Document} or another {Ripple::EmbeddedDocument}.
|
29
|
+
attr_accessor :_parent_document
|
30
|
+
|
31
|
+
# Whether the root document is unsaved.
|
32
|
+
def new?
|
33
|
+
if _root_document
|
34
|
+
_root_document.new?
|
35
|
+
else
|
36
|
+
true
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Sets this embedded documents attributes and saves the root document.
|
41
|
+
def update_attributes(attrs)
|
42
|
+
self.attributes = attrs
|
43
|
+
save
|
44
|
+
end
|
45
|
+
|
46
|
+
# Updates this embedded document's attribute and saves the
|
47
|
+
# root document, skipping validations.
|
48
|
+
def update_attribute(attribute, value)
|
49
|
+
send("#{attribute}=", value)
|
50
|
+
save(:validate => false)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Saves this embedded document by delegating to the root document.
|
54
|
+
def save(*args)
|
55
|
+
if _root_document
|
56
|
+
run_save_callbacks do
|
57
|
+
_root_document.save(*args)
|
58
|
+
end
|
59
|
+
else
|
60
|
+
raise NoRootDocument.new(self, :save)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# @private
|
65
|
+
def attributes_for_persistence
|
66
|
+
raw_attributes.merge("_type" => self.class.name)
|
67
|
+
end
|
68
|
+
|
69
|
+
# The root {Ripple::Document} to which this embedded document belongs.
|
70
|
+
def _root_document
|
71
|
+
@_parent_document.try(:_root_document)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
data/lib/ripple/i18n.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'ripple/translation'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
# Adds secondary-indexes to {Document} properties.
|
6
|
+
module Indexes
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
def inherited(subclass)
|
12
|
+
super
|
13
|
+
subclass.indexes = indexes.dup
|
14
|
+
end
|
15
|
+
|
16
|
+
# Indexes defined on the document.
|
17
|
+
def indexes
|
18
|
+
@indexes ||= {}.with_indifferent_access
|
19
|
+
end
|
20
|
+
|
21
|
+
def indexes=(idx)
|
22
|
+
@indexes = idx
|
23
|
+
end
|
24
|
+
|
25
|
+
def property(key, type, options={})
|
26
|
+
if indexed = options.delete(:index)
|
27
|
+
indexes[key] = Index.new(key, type, indexed)
|
28
|
+
end
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def index(key, type, &block)
|
33
|
+
if block_given?
|
34
|
+
indexes[key] = Index.new(key, type, &block)
|
35
|
+
else
|
36
|
+
indexes[key] = Index.new(key, type)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns indexes in a form suitable for persisting to Riak.
|
42
|
+
# @return [Hash] indexes for this document
|
43
|
+
def indexes_for_persistence(prefix = '')
|
44
|
+
Hash.new {|h,k| h[k] = Set.new }.tap do |indexes|
|
45
|
+
# Add embedded associations' indexes
|
46
|
+
self.class.embedded_associations.each do |association|
|
47
|
+
documents = instance_variable_get(association.ivar)
|
48
|
+
unless documents.nil?
|
49
|
+
Array(documents).each do |doc|
|
50
|
+
embedded_indexes = doc.indexes_for_persistence("#{prefix}#{association.name}_")
|
51
|
+
indexes.merge!(embedded_indexes) do |_,original,new|
|
52
|
+
original.merge new
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Add this document's indexes
|
59
|
+
self.class.indexes.each do |key, index|
|
60
|
+
if index.block
|
61
|
+
index_value = index.to_index_value instance_exec(&index.block)
|
62
|
+
else
|
63
|
+
index_value = index.to_index_value send(key)
|
64
|
+
end
|
65
|
+
index_value = Set[index_value] unless index_value.is_a?(Enumerable) && !index_value.is_a?(String)
|
66
|
+
indexes[prefix + index.index_key].merge index_value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Modifies the persistence chain to set indexes on the internal
|
72
|
+
# {Riak::RObject} before saving.
|
73
|
+
module DocumentMethods
|
74
|
+
extend ActiveSupport::Concern
|
75
|
+
def update_robject
|
76
|
+
robject.indexes = indexes_for_persistence
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
module ClassMethods
|
81
|
+
# Search for a document using an indexed column
|
82
|
+
# @param [Symbol] name of the index
|
83
|
+
# @param [String, Integer, Range] query to search for
|
84
|
+
def find_by_index(index_name, query)
|
85
|
+
if ["$bucket", "$key"].include?(index_name.to_s)
|
86
|
+
self.find(Ripple.client.get_index(self.bucket.name, index_name.to_s, query))
|
87
|
+
else
|
88
|
+
idx = self.indexes[index_name]
|
89
|
+
raise ArgumentError, t('index_undefined', :property => index_name, :type => self.name) if idx.nil?
|
90
|
+
self.find(Ripple.client.get_index(self.bucket.name, idx.index_key, query))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Represents a Secondary Index on a Document
|
98
|
+
class Index
|
99
|
+
include Translation
|
100
|
+
attr_reader :key, :type, :block
|
101
|
+
|
102
|
+
# Creates an index for a Document
|
103
|
+
# @param [Symbol] key the attribute key
|
104
|
+
# @param [Class] property_type the type of the associated property
|
105
|
+
# @param ['bin', 'int', String, Integer] index_type if given, the
|
106
|
+
# type of index
|
107
|
+
# @yield a block that returns the value of the index
|
108
|
+
def initialize(key, property_type, index_type=true, &block)
|
109
|
+
@key, @type, @index, @block = key, property_type, index_type, block
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# The key under which a value will be indexed
|
114
|
+
def index_key
|
115
|
+
"#{key}_#{index_type}"
|
116
|
+
end
|
117
|
+
|
118
|
+
# Converts an attribute to a value appropriate for storing in a
|
119
|
+
# secondary index.
|
120
|
+
# @param [Object] value a value of type {#type}
|
121
|
+
# @return [String, Integer, Set] a value appropriate for storing
|
122
|
+
# in a secondary index
|
123
|
+
def to_index_value(value)
|
124
|
+
value.to_ripple_index(index_type)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return ["bin", "int", nil] the type of index used for this property
|
128
|
+
# @raise [ArgumentError] if the type cannot be automatically determined
|
129
|
+
def index_type
|
130
|
+
@index_type ||= case @index
|
131
|
+
when /^bin|int$/
|
132
|
+
@index
|
133
|
+
when Class
|
134
|
+
determine_index_type(@index)
|
135
|
+
else
|
136
|
+
determine_index_type(@type)
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
def determine_index_type(itype)
|
142
|
+
if String == itype || itype < String
|
143
|
+
'bin'
|
144
|
+
elsif [Integer, Time, Date, ActiveSupport::TimeWithZone].any? {|t| t == itype || itype < t }
|
145
|
+
'int'
|
146
|
+
else
|
147
|
+
raise ArgumentError, t('index_type_unknown', :property => @key, :type => itype.name)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|