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.
Files changed (77) hide show
  1. data/LICENSE +17 -0
  2. data/README.md +182 -0
  3. data/RELEASE_NOTES.md +284 -0
  4. data/better-ripple.gemspec +55 -0
  5. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
  6. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +25 -0
  7. data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
  8. data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
  9. data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
  10. data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
  11. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  12. data/lib/rails/generators/ripple/model/templates/model.rb.erb +10 -0
  13. data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
  14. data/lib/rails/generators/ripple/observer/templates/observer.rb.erb +2 -0
  15. data/lib/rails/generators/ripple/test/templates/cucumber.rb.erb +7 -0
  16. data/lib/rails/generators/ripple/test/test_generator.rb +44 -0
  17. data/lib/rails/generators/ripple_generator.rb +79 -0
  18. data/lib/ripple.rb +86 -0
  19. data/lib/ripple/associations.rb +380 -0
  20. data/lib/ripple/associations/embedded.rb +35 -0
  21. data/lib/ripple/associations/instantiators.rb +26 -0
  22. data/lib/ripple/associations/linked.rb +65 -0
  23. data/lib/ripple/associations/many.rb +38 -0
  24. data/lib/ripple/associations/many_embedded_proxy.rb +39 -0
  25. data/lib/ripple/associations/many_linked_proxy.rb +66 -0
  26. data/lib/ripple/associations/many_reference_proxy.rb +95 -0
  27. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  28. data/lib/ripple/associations/one.rb +20 -0
  29. data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
  30. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  31. data/lib/ripple/associations/one_linked_proxy.rb +26 -0
  32. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  33. data/lib/ripple/associations/proxy.rb +118 -0
  34. data/lib/ripple/attribute_methods.rb +132 -0
  35. data/lib/ripple/attribute_methods/dirty.rb +59 -0
  36. data/lib/ripple/attribute_methods/query.rb +34 -0
  37. data/lib/ripple/attribute_methods/read.rb +28 -0
  38. data/lib/ripple/attribute_methods/write.rb +25 -0
  39. data/lib/ripple/callbacks.rb +71 -0
  40. data/lib/ripple/conflict/basic_resolver.rb +86 -0
  41. data/lib/ripple/conflict/document_hooks.rb +46 -0
  42. data/lib/ripple/conflict/resolver.rb +79 -0
  43. data/lib/ripple/conflict/test_helper.rb +34 -0
  44. data/lib/ripple/conversion.rb +29 -0
  45. data/lib/ripple/core_ext.rb +3 -0
  46. data/lib/ripple/core_ext/casting.rb +151 -0
  47. data/lib/ripple/core_ext/indexes.rb +89 -0
  48. data/lib/ripple/core_ext/object.rb +8 -0
  49. data/lib/ripple/document.rb +105 -0
  50. data/lib/ripple/document/bucket_access.rb +25 -0
  51. data/lib/ripple/document/finders.rb +131 -0
  52. data/lib/ripple/document/key.rb +35 -0
  53. data/lib/ripple/document/link.rb +30 -0
  54. data/lib/ripple/document/persistence.rb +130 -0
  55. data/lib/ripple/embedded_document.rb +63 -0
  56. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  57. data/lib/ripple/embedded_document/finders.rb +26 -0
  58. data/lib/ripple/embedded_document/persistence.rb +75 -0
  59. data/lib/ripple/i18n.rb +5 -0
  60. data/lib/ripple/indexes.rb +151 -0
  61. data/lib/ripple/inspection.rb +32 -0
  62. data/lib/ripple/locale/en.yml +26 -0
  63. data/lib/ripple/locale/fr.yml +24 -0
  64. data/lib/ripple/nested_attributes.rb +275 -0
  65. data/lib/ripple/observable.rb +28 -0
  66. data/lib/ripple/properties.rb +74 -0
  67. data/lib/ripple/property_type_mismatch.rb +12 -0
  68. data/lib/ripple/railtie.rb +26 -0
  69. data/lib/ripple/railties/ripple.rake +103 -0
  70. data/lib/ripple/serialization.rb +82 -0
  71. data/lib/ripple/test_server.rb +35 -0
  72. data/lib/ripple/timestamps.rb +25 -0
  73. data/lib/ripple/translation.rb +18 -0
  74. data/lib/ripple/validations.rb +65 -0
  75. data/lib/ripple/validations/associated_validator.rb +43 -0
  76. data/lib/ripple/version.rb +3 -0
  77. 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