ripple 0.6.1 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
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