better-ripple 1.0.0
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/LICENSE +17 -0
- data/README.md +182 -0
- data/RELEASE_NOTES.md +284 -0
- data/better-ripple.gemspec +55 -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 +44 -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 +79 -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 +103 -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
- metadata +310 -0
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'ripple/associations/instantiators'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Associations
|
5
|
+
module One
|
6
|
+
include Instantiators
|
7
|
+
|
8
|
+
def to_a
|
9
|
+
[self]
|
10
|
+
end
|
11
|
+
|
12
|
+
protected
|
13
|
+
def instantiate_target(instantiator, attrs={})
|
14
|
+
@target = klass.send(instantiator, attrs)
|
15
|
+
loaded
|
16
|
+
@target
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'ripple/associations/proxy'
|
2
|
+
require 'ripple/associations/one'
|
3
|
+
require 'ripple/associations/embedded'
|
4
|
+
|
5
|
+
module Ripple
|
6
|
+
module Associations
|
7
|
+
class OneEmbeddedProxy < Proxy
|
8
|
+
include One
|
9
|
+
include Embedded
|
10
|
+
|
11
|
+
def replace(doc)
|
12
|
+
@reflection.verify_type!(doc, @owner)
|
13
|
+
@_doc = doc.respond_to?(:attributes_for_persistence) ? doc.attributes_for_persistence : doc
|
14
|
+
assign_references(doc)
|
15
|
+
|
16
|
+
if doc.is_a?(@reflection.klass)
|
17
|
+
loaded
|
18
|
+
@target = doc
|
19
|
+
else
|
20
|
+
reset
|
21
|
+
end
|
22
|
+
|
23
|
+
@_doc
|
24
|
+
end
|
25
|
+
|
26
|
+
protected
|
27
|
+
def find_target
|
28
|
+
return nil unless @_doc
|
29
|
+
klass.instantiate(@_doc).tap do |doc|
|
30
|
+
assign_references(doc)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'ripple/associations/proxy'
|
2
|
+
require 'ripple/associations/one'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
module Associations
|
6
|
+
class OneKeyProxy < Proxy
|
7
|
+
include One
|
8
|
+
|
9
|
+
def replace(doc)
|
10
|
+
@reflection.verify_type!(doc, owner)
|
11
|
+
|
12
|
+
reset_previous_target_key_delegate
|
13
|
+
assign_new_target_key_delegate(doc)
|
14
|
+
|
15
|
+
loaded
|
16
|
+
@target = doc
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_target
|
20
|
+
klass.find(owner.key)
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
def instantiate_target(instantiator, attrs={})
|
25
|
+
@target = super
|
26
|
+
@target.key = owner.key
|
27
|
+
@target
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def reset_previous_target_key_delegate
|
32
|
+
@target.key_delegate = @target if @target
|
33
|
+
end
|
34
|
+
|
35
|
+
def assign_new_target_key_delegate(doc)
|
36
|
+
doc.class.send(:include, Ripple::Associations::KeyDelegator) unless doc.class.include?(Ripple::Associations::KeyDelegator)
|
37
|
+
owner.key_delegate = doc.key_delegate = owner
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
module KeyDelegator
|
43
|
+
attr_accessor :key_delegate
|
44
|
+
|
45
|
+
def key_delegate
|
46
|
+
@key_delegate || self
|
47
|
+
end
|
48
|
+
|
49
|
+
def key
|
50
|
+
self === key_delegate ? super : key_delegate.key
|
51
|
+
end
|
52
|
+
|
53
|
+
def key=(value)
|
54
|
+
self === key_delegate ? super(value) : key_delegate.key = value
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'ripple/associations/proxy'
|
2
|
+
require 'ripple/associations/one'
|
3
|
+
require 'ripple/associations/linked'
|
4
|
+
|
5
|
+
module Ripple
|
6
|
+
module Associations
|
7
|
+
class OneLinkedProxy < Proxy
|
8
|
+
include One
|
9
|
+
include Linked
|
10
|
+
|
11
|
+
def key
|
12
|
+
keys.first
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
def find_target
|
17
|
+
return nil if links.blank?
|
18
|
+
|
19
|
+
robjs = robjects
|
20
|
+
return nil if robjs.blank?
|
21
|
+
|
22
|
+
klass.send(:instantiate, robjs.first)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'ripple/associations/proxy'
|
2
|
+
require 'ripple/associations/one'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
module Associations
|
6
|
+
class OneStoredKeyProxy < Proxy
|
7
|
+
include One
|
8
|
+
|
9
|
+
def replace(value)
|
10
|
+
@reflection.verify_type!(value, owner)
|
11
|
+
|
12
|
+
if value
|
13
|
+
assign_key(value.key)
|
14
|
+
else
|
15
|
+
assign_key(nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
@target = value
|
19
|
+
loaded
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def key
|
25
|
+
@owner.send(key_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def assign_key(value)
|
29
|
+
@owner.send("#{key_name}=", value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def key_name
|
33
|
+
"#{@reflection.name}_key"
|
34
|
+
end
|
35
|
+
|
36
|
+
def find_target
|
37
|
+
return nil if key.blank?
|
38
|
+
|
39
|
+
klass.find(key)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'ripple/associations'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Associations
|
5
|
+
class Proxy
|
6
|
+
alias :proxy_respond_to? :respond_to?
|
7
|
+
alias :proxy_extend :extend
|
8
|
+
|
9
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
10
|
+
|
11
|
+
attr_reader :owner, :reflection, :target
|
12
|
+
|
13
|
+
alias :proxy_owner :owner
|
14
|
+
alias :proxy_target :target
|
15
|
+
alias :proxy_reflection :reflection
|
16
|
+
|
17
|
+
delegate :klass, :to => :proxy_reflection
|
18
|
+
delegate :options, :to => :proxy_reflection
|
19
|
+
delegate :collection, :to => :klass
|
20
|
+
|
21
|
+
def initialize(owner, reflection)
|
22
|
+
@owner, @reflection = owner, reflection
|
23
|
+
Array.wrap(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
24
|
+
reset
|
25
|
+
end
|
26
|
+
|
27
|
+
def inspect
|
28
|
+
load_target
|
29
|
+
target.inspect
|
30
|
+
end
|
31
|
+
|
32
|
+
def loaded?
|
33
|
+
@loaded
|
34
|
+
end
|
35
|
+
|
36
|
+
def loaded
|
37
|
+
@loaded = true
|
38
|
+
end
|
39
|
+
|
40
|
+
def nil?
|
41
|
+
load_target
|
42
|
+
target.nil?
|
43
|
+
end
|
44
|
+
|
45
|
+
def blank?
|
46
|
+
load_target
|
47
|
+
target.blank?
|
48
|
+
end
|
49
|
+
|
50
|
+
def present?
|
51
|
+
load_target
|
52
|
+
target.present?
|
53
|
+
end
|
54
|
+
|
55
|
+
def reload
|
56
|
+
reset
|
57
|
+
load_target
|
58
|
+
self unless target.nil?
|
59
|
+
end
|
60
|
+
|
61
|
+
def replace(v)
|
62
|
+
raise NotImplementedError
|
63
|
+
end
|
64
|
+
|
65
|
+
def reset
|
66
|
+
@loaded = false
|
67
|
+
@target = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def respond_to?(*args)
|
71
|
+
proxy_respond_to?(*args) || (load_target && target.respond_to?(*args))
|
72
|
+
end
|
73
|
+
|
74
|
+
def send(method, *args, &block)
|
75
|
+
if proxy_respond_to?(method)
|
76
|
+
super
|
77
|
+
else
|
78
|
+
load_target
|
79
|
+
target.send(method, *args, &block)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def ===(other)
|
84
|
+
load_target
|
85
|
+
other === target
|
86
|
+
end
|
87
|
+
|
88
|
+
def loaded_documents
|
89
|
+
loaded? ? Array.wrap(target) : []
|
90
|
+
end
|
91
|
+
|
92
|
+
def has_changed_documents?
|
93
|
+
loaded_documents.any? { |doc| doc.changed? }
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
def method_missing(method, *args, &block)
|
98
|
+
load_target
|
99
|
+
|
100
|
+
if block_given?
|
101
|
+
target.send(method, *args) { |*block_args| block.call(*block_args) }
|
102
|
+
else
|
103
|
+
target.send(method, *args)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def load_target
|
108
|
+
@target = find_target unless loaded?
|
109
|
+
loaded
|
110
|
+
@target
|
111
|
+
end
|
112
|
+
|
113
|
+
def find_target
|
114
|
+
raise NotImplementedError
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'ripple/translation'
|
2
|
+
require 'active_support/concern'
|
3
|
+
require 'active_model/attribute_methods'
|
4
|
+
require 'active_model/mass_assignment_security'
|
5
|
+
require 'ripple/attribute_methods/read'
|
6
|
+
require 'ripple/attribute_methods/write'
|
7
|
+
require 'ripple/attribute_methods/query'
|
8
|
+
require 'ripple/attribute_methods/dirty'
|
9
|
+
|
10
|
+
module Ripple
|
11
|
+
# Makes ActiveRecord-like attribute accessors based on your
|
12
|
+
# {Document}'s properties.
|
13
|
+
module AttributeMethods
|
14
|
+
include Translation
|
15
|
+
extend ActiveSupport::Concern
|
16
|
+
include ActiveModel::AttributeMethods
|
17
|
+
|
18
|
+
included do
|
19
|
+
include Read
|
20
|
+
include Write
|
21
|
+
include Query
|
22
|
+
include Dirty
|
23
|
+
include ActiveModel::MassAssignmentSecurity
|
24
|
+
|
25
|
+
attr_protected :key
|
26
|
+
end
|
27
|
+
|
28
|
+
module ClassMethods
|
29
|
+
# @private
|
30
|
+
def property(key, type, options={})
|
31
|
+
super.tap do
|
32
|
+
undefine_attribute_methods
|
33
|
+
define_attribute_methods
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generates all the attribute-related methods for properties defined
|
38
|
+
# on the document, including accessors, mutators and query methods.
|
39
|
+
def define_attribute_methods
|
40
|
+
super(properties.keys)
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def instance_method_already_implemented?(method_name)
|
46
|
+
method_defined?(method_name)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# A copy of the values of all attributes in the Document. The result
|
51
|
+
# is not memoized, so use sparingly. This does not include associated objects,
|
52
|
+
# nor embedded documents.
|
53
|
+
# @return [Hash] all document attributes, by key
|
54
|
+
def attributes
|
55
|
+
raw_attributes.reject { |k, v| !respond_to?(k) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def raw_attributes
|
59
|
+
self.class.properties.values.inject(@attributes.with_indifferent_access) do |hash, prop|
|
60
|
+
hash[prop.key] = attribute(prop.key)
|
61
|
+
hash
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Mass assign the document's attributes.
|
66
|
+
# @param [Hash] attrs the attributes to assign
|
67
|
+
# @param [Hash] options assignment options
|
68
|
+
def assign_attributes(attrs, options={})
|
69
|
+
raise ArgumentError, t('attribute_hash') unless(Hash === attrs)
|
70
|
+
|
71
|
+
unless options[:without_protection]
|
72
|
+
if method(:sanitize_for_mass_assignment).arity == 1 # ActiveModel 3.0
|
73
|
+
if options[:as]
|
74
|
+
raise ArgumentError, t('mass_assignment_roles_unsupported')
|
75
|
+
end
|
76
|
+
attrs = sanitize_for_mass_assignment(attrs)
|
77
|
+
else
|
78
|
+
mass_assignment_role = (options[:as] || :default)
|
79
|
+
attrs = sanitize_for_mass_assignment(attrs, mass_assignment_role)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
attrs.each do |k,v|
|
84
|
+
if respond_to?("#{k}=")
|
85
|
+
__send__("#{k}=",v)
|
86
|
+
else
|
87
|
+
raise ArgumentError, t('undefined_property', :prop => k, :class => self.class.name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Mass assign the document's attributes.
|
93
|
+
# @param [Hash] attrs the attributes to assign
|
94
|
+
def attributes=(attrs)
|
95
|
+
assign_attributes(attrs)
|
96
|
+
end
|
97
|
+
|
98
|
+
# @private
|
99
|
+
def raw_attributes=(attrs)
|
100
|
+
raise ArgumentError, t('attribute_hash') unless Hash === attrs
|
101
|
+
attrs.each do |k,v|
|
102
|
+
next if k.to_sym == :key
|
103
|
+
if respond_to?("#{k}=")
|
104
|
+
__send__("#{k}=",v)
|
105
|
+
else
|
106
|
+
__send__(:attribute=,k,v)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# @private
|
112
|
+
def initialize(attrs={}, options={})
|
113
|
+
super()
|
114
|
+
@attributes = attributes_from_property_defaults
|
115
|
+
assign_attributes(attrs, options)
|
116
|
+
yield self if block_given?
|
117
|
+
end
|
118
|
+
|
119
|
+
protected
|
120
|
+
# @private
|
121
|
+
def attribute_method?(attr_name)
|
122
|
+
self.class.properties.include?(attr_name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def attributes_from_property_defaults
|
126
|
+
self.class.properties.values.inject({}) do |hash, prop|
|
127
|
+
hash[prop.key] = prop.default unless prop.default.nil?
|
128
|
+
hash
|
129
|
+
end.with_indifferent_access
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'active_model/dirty'
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
module AttributeMethods
|
6
|
+
module Dirty
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
include ActiveModel::Dirty
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
# @private
|
12
|
+
def instantiate(robject)
|
13
|
+
super(robject).tap do |o|
|
14
|
+
o.changed_attributes.clear
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# @private
|
20
|
+
def really_save(*args)
|
21
|
+
if result = super
|
22
|
+
@previously_changed = changes
|
23
|
+
changed_attributes.clear
|
24
|
+
end
|
25
|
+
result
|
26
|
+
end
|
27
|
+
|
28
|
+
# @private
|
29
|
+
def reload
|
30
|
+
super.tap do
|
31
|
+
changed_attributes.clear
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# @private
|
36
|
+
def initialize(*args)
|
37
|
+
super
|
38
|
+
changed_attributes.clear
|
39
|
+
end
|
40
|
+
|
41
|
+
# Determines if the document has any chnages.
|
42
|
+
# @return [Boolean] true if this document, or any of its embedded
|
43
|
+
# documents at any level, have changed.
|
44
|
+
def changed?
|
45
|
+
super || self.class.embedded_associations.any? do |association|
|
46
|
+
send(association.name).has_changed_documents?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def attribute=(attr_name, value)
|
52
|
+
if self.class.properties.include?(attr_name.to_sym) && @attributes[attr_name] != value
|
53
|
+
attribute_will_change!(attr_name)
|
54
|
+
end
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|