ripple 0.6.1 → 0.7.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 (110) hide show
  1. data/Rakefile +61 -48
  2. data/lib/ripple.rb +5 -1
  3. data/lib/ripple/core_ext/casting.rb +3 -0
  4. data/lib/ripple/document.rb +6 -2
  5. data/lib/ripple/document/associations.rb +154 -0
  6. data/lib/ripple/document/{persistence/callbacks.rb → associations/embedded.rb} +20 -24
  7. data/lib/ripple/document/associations/instantiators.rb +41 -0
  8. data/lib/{riak/util/translation.rb → ripple/document/associations/linked.rb} +14 -11
  9. data/lib/ripple/document/associations/many.rb +52 -0
  10. data/lib/ripple/document/associations/many_embedded_proxy.rb +49 -0
  11. data/lib/{riak/i18n.rb → ripple/document/associations/one.rb} +18 -2
  12. data/lib/ripple/document/associations/one_embedded_proxy.rb +41 -0
  13. data/lib/ripple/document/associations/proxy.rb +125 -0
  14. data/lib/ripple/document/attribute_methods.rb +8 -1
  15. data/lib/ripple/document/attribute_methods/read.rb +4 -0
  16. data/lib/ripple/document/attribute_methods/write.rb +4 -0
  17. data/lib/ripple/document/bucket_access.rb +1 -1
  18. data/lib/ripple/document/callbacks.rb +75 -0
  19. data/lib/ripple/document/finders.rb +50 -3
  20. data/lib/ripple/document/persistence.rb +14 -6
  21. data/lib/ripple/document/validations.rb +35 -7
  22. data/lib/ripple/document/validations/associated_validator.rb +37 -0
  23. data/lib/ripple/embedded_document.rb +8 -2
  24. data/lib/{riak/map_reduce_error.rb → ripple/embedded_document/conversion.rb} +19 -5
  25. data/lib/{riak/invalid_response.rb → ripple/embedded_document/finders.rb} +17 -8
  26. data/lib/ripple/embedded_document/persistence.rb +75 -13
  27. data/lib/ripple/locale/en.yml +7 -1
  28. data/{spec/riak/net_http_backend_spec.rb → lib/ripple/railtie.rb} +17 -13
  29. data/spec/fixtures/config.yml +3 -0
  30. data/spec/integration/ripple/associations_spec.rb +81 -0
  31. data/spec/integration/ripple/persistence_spec.rb +54 -0
  32. data/spec/ripple/associations/many_embedded_proxy_spec.rb +124 -0
  33. data/spec/ripple/associations/one_embedded_proxy_spec.rb +130 -0
  34. data/spec/ripple/associations/proxy_spec.rb +78 -0
  35. data/spec/ripple/associations_spec.rb +111 -0
  36. data/spec/ripple/attribute_methods_spec.rb +37 -16
  37. data/spec/ripple/bucket_access_spec.rb +3 -14
  38. data/spec/ripple/callbacks_spec.rb +53 -9
  39. data/spec/ripple/document_spec.rb +22 -6
  40. data/spec/ripple/embedded_document/conversion_spec.rb +35 -0
  41. data/spec/{riak/headers_spec.rb → ripple/embedded_document/finders_spec.rb} +17 -14
  42. data/spec/ripple/embedded_document/persistence_spec.rb +86 -0
  43. data/spec/ripple/embedded_document_spec.rb +1 -26
  44. data/spec/ripple/finders_spec.rb +66 -30
  45. data/spec/ripple/persistence_spec.rb +33 -21
  46. data/spec/ripple/properties_spec.rb +1 -7
  47. data/spec/ripple/ripple_spec.rb +10 -0
  48. data/spec/ripple/timestamps_spec.rb +12 -19
  49. data/spec/ripple/validations_spec.rb +48 -6
  50. data/spec/spec_helper.rb +4 -10
  51. data/spec/support/associations/proxies.rb +16 -0
  52. data/spec/support/integration.rb +4 -0
  53. data/spec/support/mocks.rb +3 -0
  54. data/spec/support/models/address.rb +8 -0
  55. data/spec/support/models/box.rb +6 -0
  56. data/spec/support/models/cardboard_box.rb +3 -0
  57. data/spec/support/models/clock.rb +6 -0
  58. data/spec/support/models/customer.rb +4 -0
  59. data/spec/support/models/email.rb +4 -0
  60. data/spec/support/models/family.rb +14 -0
  61. data/spec/support/models/favorite.rb +4 -0
  62. data/spec/support/models/invoice.rb +6 -0
  63. data/spec/support/models/late_invoice.rb +3 -0
  64. data/spec/support/models/note.rb +4 -0
  65. data/spec/support/models/page.rb +4 -0
  66. data/spec/support/models/paid_invoice.rb +4 -0
  67. data/spec/support/models/tree.rb +3 -0
  68. data/spec/support/models/user.rb +6 -0
  69. data/spec/support/models/widget.rb +6 -0
  70. metadata +111 -138
  71. data/.document +0 -5
  72. data/.gitignore +0 -26
  73. data/CONTRIBUTORS.textile +0 -5
  74. data/LICENSE +0 -13
  75. data/README.textile +0 -128
  76. data/RELEASE_NOTES.textile +0 -68
  77. data/VERSION +0 -1
  78. data/lib/riak.rb +0 -46
  79. data/lib/riak/bucket.rb +0 -157
  80. data/lib/riak/client.rb +0 -139
  81. data/lib/riak/client/curb_backend.rb +0 -82
  82. data/lib/riak/client/http_backend.rb +0 -209
  83. data/lib/riak/client/net_http_backend.rb +0 -49
  84. data/lib/riak/failed_request.rb +0 -37
  85. data/lib/riak/link.rb +0 -73
  86. data/lib/riak/locale/en.yml +0 -37
  87. data/lib/riak/map_reduce.rb +0 -248
  88. data/lib/riak/robject.rb +0 -258
  89. data/lib/riak/util/escape.rb +0 -12
  90. data/lib/riak/util/fiber1.8.rb +0 -48
  91. data/lib/riak/util/headers.rb +0 -44
  92. data/lib/riak/util/multipart.rb +0 -52
  93. data/lib/riak/walk_spec.rb +0 -117
  94. data/ripple.gemspec +0 -169
  95. data/spec/fixtures/cat.jpg +0 -0
  96. data/spec/fixtures/multipart-blank.txt +0 -7
  97. data/spec/fixtures/multipart-with-body.txt +0 -16
  98. data/spec/riak/bucket_spec.rb +0 -230
  99. data/spec/riak/client_spec.rb +0 -174
  100. data/spec/riak/curb_backend_spec.rb +0 -50
  101. data/spec/riak/escape_spec.rb +0 -17
  102. data/spec/riak/http_backend_spec.rb +0 -131
  103. data/spec/riak/link_spec.rb +0 -82
  104. data/spec/riak/map_reduce_spec.rb +0 -352
  105. data/spec/riak/multipart_spec.rb +0 -36
  106. data/spec/riak/object_spec.rb +0 -532
  107. data/spec/riak/walk_spec_spec.rb +0 -208
  108. data/spec/spec.opts +0 -1
  109. data/spec/support/http_backend_implementation_examples.rb +0 -215
  110. data/spec/support/mock_server.rb +0 -58
@@ -14,6 +14,27 @@
14
14
  require 'ripple'
15
15
 
16
16
  module Ripple
17
+
18
+ # Raised by <tt>find!</tt> when a document cannot be found with the given key.
19
+ # begin
20
+ # Example.find!('badkey')
21
+ # rescue Ripple::DocumentNotFound
22
+ # puts 'No Document here!'
23
+ # end
24
+ class DocumentNotFound < StandardError
25
+ include Translation
26
+ def initialize(keys, found)
27
+ if keys.empty?
28
+ super(t("document_not_found.no_key"))
29
+ elsif keys.one?
30
+ super(t("document_not_found.one_key", :key => keys.first))
31
+ else
32
+ missing = keys - found.compact.map(&:key)
33
+ super(t("document_not_found.many_keys", :keys => missing.join(', ')))
34
+ end
35
+ end
36
+ end
37
+
17
38
  module Document
18
39
  module Finders
19
40
  extend ActiveSupport::Concern
@@ -35,10 +56,33 @@ module Ripple
35
56
  # @return [Array<Document>] a list of found documents, including nil for missing documents
36
57
  def find(*args)
37
58
  args.flatten!
38
- return [] if args.length == 0
39
- return find_one(args.first) if args.length == 1
59
+ return nil if args.empty?
60
+ return find_one(args.first) if args.one?
40
61
  args.map {|key| find_one(key) }
41
62
  end
63
+
64
+ # Retrieve single or multiple documents from Riak
65
+ # but raise Ripple::DocumentNotFound if a key can
66
+ # not be found in the bucket.
67
+ def find!(*args)
68
+ found = find(*args)
69
+ raise DocumentNotFound.new(args, found) if !found || Array(found).include?(nil)
70
+ found
71
+ end
72
+
73
+ # Find the first object using the first key in the
74
+ # bucket's keys using find. You should not expect to
75
+ # actually get the first object you added to the bucket.
76
+ # This is just a convenience method.
77
+ def first
78
+ find(bucket.keys.first)
79
+ end
80
+
81
+ # Find the first object using the first key in the
82
+ # bucket's keys using find!
83
+ def first!
84
+ find!(bucket.keys.first)
85
+ end
42
86
 
43
87
  # Find all documents in the Document's bucket and return them.
44
88
  # @overload all()
@@ -74,7 +118,10 @@ module Ripple
74
118
 
75
119
  def instantiate(robject)
76
120
  klass = robject.data['_type'].constantize rescue self
77
- klass.new(robject.data.merge('key' => robject.key)).tap do |doc|
121
+ data = {'key' => robject.key}
122
+ data.reverse_merge!(robject.data) rescue data
123
+ klass.new(data).tap do |doc|
124
+ doc.key = data['key']
78
125
  doc.instance_variable_set(:@new, false)
79
126
  doc.instance_variable_set(:@robject, robject)
80
127
  end
@@ -18,11 +18,20 @@ module Ripple
18
18
  module Persistence
19
19
  extend ActiveSupport::Concern
20
20
  extend ActiveSupport::Autoload
21
+
22
+ module ClassMethods
23
+
24
+ # Instantiates a new record, applies attributes from a block, and saves it
25
+ def create(attrs={}, &block)
26
+ new(attrs, &block).tap {|s| s.save}
27
+ end
21
28
 
22
- autoload :Callbacks
23
-
24
- included do
25
- include Ripple::Document::Persistence::Callbacks
29
+ # Destroys all records one at a time.
30
+ # Place holder while :delete to bucket is being developed.
31
+ def destroy_all
32
+ all(&:destroy)
33
+ end
34
+
26
35
  end
27
36
 
28
37
  module InstanceMethods
@@ -36,7 +45,6 @@ module Ripple
36
45
  def new?
37
46
  @new || false
38
47
  end
39
- alias :new_record? :new?
40
48
 
41
49
  # Saves the document in Riak.
42
50
  # @return [true,false] whether the document succeeded in saving
@@ -73,7 +81,7 @@ module Ripple
73
81
  def freeze
74
82
  @attributes.freeze; super
75
83
  end
76
-
84
+
77
85
  protected
78
86
  attr_writer :robject
79
87
 
@@ -14,10 +14,31 @@
14
14
  require 'ripple'
15
15
 
16
16
  module Ripple
17
+
18
+ # Raised by <tt>save!</tt> when the document is invalid. Use the
19
+ # +document+ method to retrieve the document which did not validate.
20
+ # begin
21
+ # invalid_document.save!
22
+ # rescue Ripple::DocumentInvalid => invalid
23
+ # puts invalid.document.errors
24
+ # end
25
+ class DocumentInvalid < StandardError
26
+ include Translation
27
+ attr_reader :document
28
+ def initialize(document)
29
+ @document = document
30
+ errors = @document.errors.full_messages.join(", ")
31
+ super(t("document_invalid", :errors => errors))
32
+ end
33
+ end
34
+
17
35
  module Document
18
36
  module Validations
19
37
  extend ActiveSupport::Concern
38
+ extend ActiveSupport::Autoload
20
39
  include ActiveModel::Validations
40
+
41
+ autoload :AssociatedValidator
21
42
 
22
43
  module ClassMethods
23
44
  # @private
@@ -25,19 +46,26 @@ module Ripple
25
46
  prop = super
26
47
  validates key, prop.validation_options unless prop.validation_options.blank?
27
48
  end
49
+
50
+ # Instantiates a new record, applies attributes from a block, and saves it
51
+ # Raises Ripple::DocumentInvalid if the record did not save
52
+ def create!(attrs={}, &block)
53
+ obj = create(attrs, &block)
54
+ (raise Ripple::DocumentInvalid.new(obj) if obj.new?) || obj
55
+ end
28
56
  end
29
57
 
30
58
  module InstanceMethods
31
59
  # @private
32
- def save
33
- valid? && super
60
+ def save(options={:validate => true})
61
+ return false if options[:validate] && !valid?
62
+ super()
34
63
  end
35
-
36
- # @private
37
- def valid?
38
- @_on_validate = new? ? :create : :update
39
- super
64
+
65
+ def save!
66
+ (raise Ripple::DocumentInvalid.new(self) unless save) || true
40
67
  end
68
+
41
69
  end
42
70
  end
43
71
  end
@@ -0,0 +1,37 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ #
15
+ # Taken from ActiveRecord::Validations::AssociatedValidators
16
+ #
17
+
18
+ require 'ripple'
19
+
20
+ module Ripple
21
+ module Document
22
+ module Validations
23
+ class AssociatedValidator < ActiveModel::EachValidator
24
+ def validate_each(record, attribute, value)
25
+ return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
26
+ record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
27
+ end
28
+ end
29
+
30
+ module ClassMethods
31
+ def validates_associated(*attr_names)
32
+ validates_with AssociatedValidator, _merge_attributes(attr_names)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -18,16 +18,22 @@ module Ripple
18
18
  extend ActiveSupport::Concern
19
19
  extend ActiveSupport::Autoload
20
20
 
21
+ autoload :Conversion
22
+ autoload :Finders
21
23
  autoload :Persistence
22
24
  include Translation
23
25
 
24
26
  included do
25
27
  extend ActiveModel::Naming
26
28
  extend Ripple::Document::Properties
27
- include Persistence
28
29
  include Ripple::Document::AttributeMethods
29
30
  include Ripple::Document::Timestamps
30
31
  include Ripple::Document::Validations
32
+ include Ripple::Document::Associations
33
+ include Ripple::Document::Callbacks
34
+ include Conversion
35
+ include Finders
36
+ include Persistence
31
37
  end
32
38
 
33
39
  module ClassMethods
@@ -36,4 +42,4 @@ module Ripple
36
42
  end
37
43
  end
38
44
  end
39
- end
45
+ end
@@ -11,10 +11,24 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- require 'riak'
14
+ require 'ripple'
15
15
 
16
- module Riak
17
- # Raised when an error occurred in the Javascript map-reduce chain.
18
- # The message will be the body of the JSON error response.
19
- class MapReduceError < StandardError; end
16
+ module Ripple
17
+ module EmbeddedDocument
18
+ module Conversion
19
+ extend ActiveSupport::Concern
20
+
21
+ module InstanceMethods
22
+ include ActiveModel::Conversion
23
+
24
+ def to_key
25
+ new? ? nil : [key]
26
+ end
27
+
28
+ def to_param
29
+ key
30
+ end
31
+ end
32
+ end
33
+ end
20
34
  end
@@ -11,15 +11,24 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- require 'riak'
14
+ require 'ripple'
15
15
 
16
- module Riak
17
- # Raised when Riak returns a response that is in an unexpected format
18
- class InvalidResponse < StandardError
19
- def initialize(expected, received, extra="")
20
- expected = expected.inspect if Hash === expected
21
- received = received.inspect if Hash === received
22
- super "Expected #{expected} but received #{received} from Riak #{extra}"
16
+ module Ripple
17
+ module EmbeddedDocument
18
+ module Finders
19
+ extend ActiveSupport::Concern
20
+
21
+ module ClassMethods
22
+ def instantiate(attrs)
23
+ begin
24
+ klass = attrs['_type'].present? ? attrs['_type'].constantize : self
25
+ klass.new(attrs)
26
+ rescue NameError
27
+ new(attrs)
28
+ end
29
+ end
30
+ end
31
+
23
32
  end
24
33
  end
25
34
  end
@@ -14,32 +14,94 @@
14
14
  require 'ripple'
15
15
 
16
16
  module Ripple
17
+ class NoRootDocument < StandardError
18
+ include Translation
19
+ def initialize(doc, method)
20
+ super(t("no_root_document", :doc => doc.inspect, :method => method))
21
+ end
22
+ end
23
+
17
24
  module EmbeddedDocument
18
25
  module Persistence
19
26
  extend ActiveSupport::Concern
20
-
21
- included do
22
- attr_accessor :_root_document
27
+
28
+ module ClassMethods
29
+ def embedded_in(parent)
30
+ define_method(parent) { @_parent_document }
31
+ end
23
32
  end
24
-
33
+
25
34
  module InstanceMethods
26
- # Delegates to the root document
35
+
36
+ attr_reader :_parent_document
37
+
38
+ def embeddable?
39
+ self.class.embeddable?
40
+ end
41
+
27
42
  def new?
28
- if @_root_document
29
- @_root_document.new?
30
- else
43
+ if _root_document?
31
44
  super
45
+ elsif @_root_document
46
+ _root_document.new?
47
+ else
48
+ true
32
49
  end
33
50
  end
34
-
35
- # Delegates to the root document
36
- def save
37
- if @_root_document
38
- @_root_document.save
51
+
52
+ # because alias_method doesn't like super
53
+ def new_record?; new?; end
54
+
55
+ # for ActiveModel::Conversion
56
+ def persisted?; !new?; end
57
+
58
+ def save(*args)
59
+ if _root_document?
60
+ super
61
+ elsif @_root_document
62
+ _root_document.save(*args)
39
63
  else
64
+ raise NoRootDocument.new(self, :save)
65
+ end
66
+ end
67
+
68
+ def save!(*args)
69
+ if _root_document?
40
70
  super
71
+ elsif @_root_document
72
+ _root_document.save!(*args)
73
+ else
74
+ raise NoRootDocument.new(self, :save!)
41
75
  end
42
76
  end
77
+
78
+ def _root_document
79
+ embeddable? ? @_root_document : self
80
+ end
81
+
82
+ def _root_document?
83
+ _root_document === self
84
+ end
85
+
86
+ def attributes_for_persistence
87
+ attributes.merge("_type" => self.class.name).merge(embedded_attributes_for_persistence)
88
+ end
89
+
90
+ def _parent_document=(value)
91
+ @_root_document = value._root_document
92
+ @_parent_document = value
93
+ end
94
+
95
+ protected
96
+
97
+ def embedded_attributes_for_persistence
98
+ embedded_associations.inject({}) do |attrs, association|
99
+ if documents = instance_variable_get(association.ivar)
100
+ attrs[association.name] = documents.is_a?(Array) ? documents.map(&:attributes_for_persistence) : documents.attributes_for_persistence
101
+ end
102
+ attrs
103
+ end
104
+ end
43
105
  end
44
106
  end
45
107
  end
@@ -14,4 +14,10 @@
14
14
  en:
15
15
  ripple:
16
16
  property_type_mismatch: "Cannot cast {{value}} into a {{class}}"
17
- attribute_hash: "value of attributes must be a Hash"
17
+ attribute_hash: "value of attributes must be a Hash"
18
+ document_invalid: "Validation failed: {{errors}}"
19
+ document_not_found:
20
+ no_key: "Couldn't find document without a key"
21
+ one_key: "Couldn't find document with key: {{key}}"
22
+ many_keys: "Couldn't find documents with keys: {{keys}}"
23
+ no_root_document: "You cannot call {{method}} on {{doc}} without a root document"
@@ -11,18 +11,22 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
- require File.expand_path("../spec_helper", File.dirname(__FILE__))
15
-
16
- describe Riak::Client::NetHTTPBackend do
17
- before :each do
18
- @client = Riak::Client.new
19
- @backend = Riak::Client::NetHTTPBackend.new(@client)
20
- FakeWeb.allow_net_connect = false
21
- end
14
+ require 'ripple'
15
+ require 'rails'
22
16
 
23
- def setup_http_mock(method, uri, options={})
24
- FakeWeb.register_uri(method, uri, options)
17
+ # require in gemfile using
18
+ # <tt>gem "ripple", :require_as => ["ripple", "ripple/railtie"]</tt>
19
+ module Ripple
20
+ class Railtie < Rails::Railtie
21
+ railtie_name :ripple
22
+
23
+ initializer "ripple.configure_rails_initialization" do
24
+ Ripple.load_configuration
25
+ end
25
26
  end
26
-
27
- it_should_behave_like "HTTP backend"
28
- end
27
+
28
+ def self.load_configuration
29
+ config_file = Rails.root.join('config', 'database.yml')
30
+ self.config = YAML.load_file(File.expand_path config_file).with_indifferent_access[:ripple][Rails.env]
31
+ end
32
+ end