ripple 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. data/.document +5 -0
  2. data/.gitignore +26 -0
  3. data/LICENSE +13 -0
  4. data/README.textile +126 -0
  5. data/RELEASE_NOTES.textile +24 -0
  6. data/Rakefile +61 -0
  7. data/VERSION +1 -0
  8. data/lib/riak.rb +45 -0
  9. data/lib/riak/bucket.rb +105 -0
  10. data/lib/riak/client.rb +138 -0
  11. data/lib/riak/client/curb_backend.rb +63 -0
  12. data/lib/riak/client/http_backend.rb +209 -0
  13. data/lib/riak/client/net_http_backend.rb +49 -0
  14. data/lib/riak/failed_request.rb +37 -0
  15. data/lib/riak/i18n.rb +15 -0
  16. data/lib/riak/invalid_response.rb +25 -0
  17. data/lib/riak/link.rb +54 -0
  18. data/lib/riak/locale/en.yml +37 -0
  19. data/lib/riak/map_reduce.rb +240 -0
  20. data/lib/riak/map_reduce_error.rb +20 -0
  21. data/lib/riak/robject.rb +234 -0
  22. data/lib/riak/util/headers.rb +44 -0
  23. data/lib/riak/util/multipart.rb +52 -0
  24. data/lib/riak/util/translation.rb +29 -0
  25. data/lib/riak/walk_spec.rb +113 -0
  26. data/lib/ripple.rb +48 -0
  27. data/lib/ripple/core_ext/casting.rb +96 -0
  28. data/lib/ripple/document.rb +60 -0
  29. data/lib/ripple/document/attribute_methods.rb +111 -0
  30. data/lib/ripple/document/attribute_methods/dirty.rb +52 -0
  31. data/lib/ripple/document/attribute_methods/query.rb +49 -0
  32. data/lib/ripple/document/attribute_methods/read.rb +38 -0
  33. data/lib/ripple/document/attribute_methods/write.rb +36 -0
  34. data/lib/ripple/document/bucket_access.rb +38 -0
  35. data/lib/ripple/document/finders.rb +84 -0
  36. data/lib/ripple/document/persistence.rb +93 -0
  37. data/lib/ripple/document/persistence/callbacks.rb +48 -0
  38. data/lib/ripple/document/properties.rb +85 -0
  39. data/lib/ripple/document/validations.rb +44 -0
  40. data/lib/ripple/embedded_document.rb +38 -0
  41. data/lib/ripple/embedded_document/persistence.rb +46 -0
  42. data/lib/ripple/i18n.rb +15 -0
  43. data/lib/ripple/locale/en.yml +16 -0
  44. data/lib/ripple/property_type_mismatch.rb +23 -0
  45. data/lib/ripple/translation.rb +24 -0
  46. data/ripple.gemspec +159 -0
  47. data/spec/fixtures/cat.jpg +0 -0
  48. data/spec/fixtures/multipart-blank.txt +7 -0
  49. data/spec/fixtures/multipart-with-body.txt +16 -0
  50. data/spec/riak/bucket_spec.rb +141 -0
  51. data/spec/riak/client_spec.rb +169 -0
  52. data/spec/riak/curb_backend_spec.rb +50 -0
  53. data/spec/riak/headers_spec.rb +34 -0
  54. data/spec/riak/http_backend_spec.rb +136 -0
  55. data/spec/riak/link_spec.rb +50 -0
  56. data/spec/riak/map_reduce_spec.rb +347 -0
  57. data/spec/riak/multipart_spec.rb +36 -0
  58. data/spec/riak/net_http_backend_spec.rb +28 -0
  59. data/spec/riak/object_spec.rb +444 -0
  60. data/spec/riak/walk_spec_spec.rb +208 -0
  61. data/spec/ripple/attribute_methods_spec.rb +149 -0
  62. data/spec/ripple/bucket_access_spec.rb +48 -0
  63. data/spec/ripple/callbacks_spec.rb +86 -0
  64. data/spec/ripple/document_spec.rb +35 -0
  65. data/spec/ripple/embedded_document_spec.rb +52 -0
  66. data/spec/ripple/finders_spec.rb +146 -0
  67. data/spec/ripple/persistence_spec.rb +89 -0
  68. data/spec/ripple/properties_spec.rb +195 -0
  69. data/spec/ripple/ripple_spec.rb +43 -0
  70. data/spec/ripple/validations_spec.rb +64 -0
  71. data/spec/spec.opts +1 -0
  72. data/spec/spec_helper.rb +32 -0
  73. data/spec/support/http_backend_implementation_examples.rb +215 -0
  74. data/spec/support/mock_server.rb +58 -0
  75. metadata +221 -0
@@ -0,0 +1,111 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ # Makes ActiveRecord-like attribute accessors based on your
19
+ # {Document}'s properties.
20
+ module AttributeMethods
21
+ extend ActiveSupport::Concern
22
+ extend ActiveSupport::Autoload
23
+ include ActiveModel::AttributeMethods
24
+
25
+ autoload :Read
26
+ autoload :Write
27
+ autoload :Query
28
+ autoload :Dirty
29
+
30
+ included do
31
+ attr_accessor :key
32
+ include Read
33
+ include Write
34
+ include Query
35
+ include Dirty
36
+ end
37
+
38
+ module ClassMethods
39
+ # @private
40
+ def property(key, type, options={})
41
+ undefine_attribute_methods
42
+ super
43
+ end
44
+
45
+ # Generates all the attribute-related methods for properties defined
46
+ # on the document, including accessors, mutators and query methods.
47
+ def define_attribute_methods
48
+ super(properties.keys)
49
+ end
50
+ end
51
+
52
+ module InstanceMethods
53
+ # A copy of the values of all attributes in the Document. The result
54
+ # is not memoized, so use sparingly. This does not include associated objects,
55
+ # nor embedded documents.
56
+ # @return [Hash] all document attributes, by key
57
+ def attributes
58
+ self.class.properties.values.inject({}) do |hash, prop|
59
+ hash[prop.key] = attribute(prop.key)
60
+ hash
61
+ end.with_indifferent_access
62
+ end
63
+
64
+ # Mass assign the document's attributes.
65
+ # @param [Hash] attrs the attributes to assign
66
+ def attributes=(attrs)
67
+ raise ArgumentError, "value of attributes must be a Hash" unless Hash === attrs
68
+ attrs.each do |k,v|
69
+ if respond_to?("#{k}=")
70
+ __send__("#{k}=",v)
71
+ else
72
+ __send__(:attribute=,k,v)
73
+ end
74
+ end
75
+ end
76
+
77
+ # @private
78
+ def initialize(attrs={})
79
+ super()
80
+ @attributes = attributes_from_property_defaults
81
+ self.attributes = attrs
82
+ end
83
+
84
+ # @private
85
+ def method_missing(method, *args, &block)
86
+ self.class.define_attribute_methods
87
+ super
88
+ end
89
+
90
+ # @private
91
+ def respond_to?(*args)
92
+ self.class.define_attribute_methods
93
+ super
94
+ end
95
+
96
+ protected
97
+ # @private
98
+ def attribute_method?(attr_name)
99
+ self.class.properties.include?(attr_name)
100
+ end
101
+
102
+ def attributes_from_property_defaults
103
+ self.class.properties.values.inject({}) do |hash, prop|
104
+ hash[prop.key] = prop.default if prop.default
105
+ hash
106
+ end.with_indifferent_access
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,52 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module AttributeMethods
19
+ module Dirty
20
+ extend ActiveSupport::Concern
21
+ include ActiveModel::Dirty
22
+
23
+ # @private
24
+ def save
25
+ if result = super
26
+ changed_attributes.clear
27
+ end
28
+ result
29
+ end
30
+
31
+ # @private
32
+ def reload
33
+ returning super do
34
+ changed_attributes.clear
35
+ end
36
+ end
37
+
38
+ # @private
39
+ def initialize(attrs={})
40
+ super(attrs)
41
+ changed_attributes.clear
42
+ end
43
+
44
+ private
45
+ def attribute=(attr_name, value)
46
+ attribute_will_change!(attr_name)
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,49 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module AttributeMethods
19
+ module Query
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ attribute_method_suffix "?"
24
+ end
25
+
26
+ private
27
+ # Based on code from ActiveRecord
28
+ def attribute?(attr_name)
29
+ unless value = attribute(attr_name)
30
+ false
31
+ else
32
+ prop = self.class.properties[attr_name]
33
+ if prop.nil?
34
+ if Numeric === value || value !~ /[^0-9]/
35
+ !value.to_i.zero?
36
+ else
37
+ Boolean.ripple_cast(value) || value.present?
38
+ end
39
+ elsif prop.type <= Numeric
40
+ !value.zero?
41
+ else
42
+ value.present?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module AttributeMethods
19
+ module Read
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ attribute_method_suffix ""
24
+ end
25
+
26
+ private
27
+ def attribute(attr_name)
28
+ if @attributes.include?(attr_name)
29
+ value = @attributes[attr_name]
30
+ value.duplicable? ? value.clone : value
31
+ else
32
+ nil
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module AttributeMethods
19
+ module Write
20
+ extend ActiveSupport::Concern
21
+
22
+ included do
23
+ attribute_method_suffix "="
24
+ end
25
+
26
+ private
27
+ def attribute=(attr_name, value)
28
+ if prop = self.class.properties[attr_name]
29
+ value = prop.type_cast(value)
30
+ end
31
+ @attributes[attr_name] = value
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,38 @@
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
+ require 'ripple/document'
15
+
16
+ module Ripple
17
+ module Document
18
+ # Similar to ActiveRecord's tables or MongoMapper's collections, we
19
+ # provide a sane default bucket in which to store your documents.
20
+ module BucketAccess
21
+ # @return [String] The bucket name assigned to the document class. Subclasses will inherit their bucket name from their parent class unless they redefine it.
22
+ def bucket_name
23
+ superclass.respond_to?(:bucket_name) ? superclass.bucket_name : model_name.plural
24
+ end
25
+
26
+ # @return [Riak::Bucket] The bucket assigned to this class.
27
+ def bucket
28
+ Ripple.client[bucket_name, {:keys => false}]
29
+ end
30
+
31
+ # Set the bucket name for this class and its subclasses.
32
+ # @param [String] value the new bucket name
33
+ def bucket_name=(value)
34
+ metaclass.send(:define_method, :bucket_name){ value }
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,84 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module Finders
19
+ extend ActiveSupport::Concern
20
+
21
+ module ClassMethods
22
+ # Retrieve single or multiple documents from Riak.
23
+ # @overload find(key)
24
+ # Find a single document.
25
+ # @param [String] key the key of a document to find
26
+ # @return [Document] the found document, or nil
27
+ # @overload find(key1, key2, ...)
28
+ # Find a list of documents.
29
+ # @param [String] key1 the key of a document to find
30
+ # @param [String] key2 the key of a document to find
31
+ # @return [Array<Document>] a list of found documents, including nil for missing documents
32
+ # @overload find(keylist)
33
+ # Find a list of documents.
34
+ # @param [Array<String>] keylist an array of keys to find
35
+ # @return [Array<Document>] a list of found documents, including nil for missing documents
36
+ def find(*args)
37
+ args.flatten!
38
+ return [] if args.length == 0
39
+ return find_one(args.first) if args.length == 1
40
+ args.map {|key| find_one(key) }
41
+ end
42
+
43
+ # Find all documents in the Document's bucket and return them.
44
+ # @overload all()
45
+ # Get all documents and return them in an array.
46
+ # @return [Array<Document>] all found documents in the bucket
47
+ # @overload all() {|doc| ... }
48
+ # Stream all documents in the bucket through the block.
49
+ # TODO: Make this work with the CurbBackend (currently single-request oriented).
50
+ # @yield [Document] doc a found document
51
+ def all
52
+ if block_given?
53
+ bucket.keys do |key|
54
+ obj = find_one(key)
55
+ yield obj if obj
56
+ end
57
+ []
58
+ else
59
+ bucket.keys.inject([]) do |acc, k|
60
+ obj = find_one(k)
61
+ obj ? acc << obj : acc
62
+ end
63
+ end
64
+ end
65
+
66
+ private
67
+ def find_one(key)
68
+ instantiate(bucket.get(key))
69
+ rescue Riak::FailedRequest => fr
70
+ return nil if fr.code.to_i == 404
71
+ raise fr
72
+ end
73
+
74
+ def instantiate(robject)
75
+ klass = robject.data['_type'].constantize rescue self
76
+ klass.new(robject.data.merge('key' => robject.key)).tap do |doc|
77
+ doc.instance_variable_set(:@new, false)
78
+ doc.instance_variable_set(:@robject, robject)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,93 @@
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
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Document
18
+ module Persistence
19
+ extend ActiveSupport::Concern
20
+ extend ActiveSupport::Autoload
21
+
22
+ autoload :Callbacks
23
+
24
+ included do
25
+ include Callbacks
26
+ end
27
+
28
+ module InstanceMethods
29
+ # @private
30
+ def initialize
31
+ super
32
+ @new = true
33
+ end
34
+
35
+ # Determines whether this is a new document.
36
+ def new?
37
+ @new || false
38
+ end
39
+ alias :new_record? :new?
40
+
41
+ # Saves the document in Riak.
42
+ # @return [true,false] whether the document succeeded in saving
43
+ def save
44
+ robject.key = key if robject.key != key
45
+ robject.data = attributes_for_persistence
46
+ robject.store
47
+ self.key = robject.key
48
+ @new = false
49
+ true
50
+ rescue Riak::FailedRequest
51
+ false
52
+ end
53
+
54
+ # Reloads the document from Riak
55
+ # @return self
56
+ def reload
57
+ return self if new?
58
+ robject.reload(:force => true)
59
+ @attributes.merge!(@robject.data)
60
+ self
61
+ end
62
+
63
+ # Deletes the document from Riak and freezes this instance
64
+ def destroy
65
+ robject.delete unless new?
66
+ freeze
67
+ true
68
+ rescue Riak::FailedRequest
69
+ false
70
+ end
71
+
72
+ # Freezes the document, preventing further modification.
73
+ def freeze
74
+ @attributes.freeze; super
75
+ end
76
+
77
+ protected
78
+ attr_writer :robject
79
+
80
+ def robject
81
+ @robject ||= Riak::RObject.new(self.class.bucket, key).tap do |obj|
82
+ obj.content_type = "application/json"
83
+ end
84
+ end
85
+
86
+ private
87
+ def attributes_for_persistence
88
+ attributes.merge("_type" => self.class.name)
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end