ripple 0.5.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 (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