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,44 @@
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 'riak'
15
+
16
+ module Riak
17
+ module Util
18
+ # Represents headers from an HTTP response
19
+ class Headers
20
+ include Net::HTTPHeader
21
+
22
+ def initialize
23
+ initialize_http_header({})
24
+ end
25
+
26
+ # Parse a single header line into its key and value
27
+ # @param [String] chunk a single header line
28
+ def self.parse(chunk)
29
+ line = chunk.strip
30
+ # thanks Net::HTTPResponse
31
+ return [nil,nil] if chunk =~ /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in
32
+ m = /\A([^:]+):\s*/.match(line)
33
+ [m[1], m.post_match] rescue [nil, nil]
34
+ end
35
+
36
+ # Parses a header line and adds it to the header collection
37
+ # @param [String] chunk a single header line
38
+ def parse(chunk)
39
+ key, value = self.class.parse(chunk)
40
+ add_field(key, value) if key && value
41
+ end
42
+ end
43
+ end
44
+ 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 'riak'
15
+
16
+ module Riak
17
+ module Util
18
+ # Utility methods for handling multipart/mixed responses
19
+ module Multipart
20
+ extend self
21
+ # Parses a multipart/mixed body into its constituent parts, including nested multipart/mixed sections
22
+ # @param [String] data the multipart body data
23
+ # @param [String] boundary the boundary string given in the Content-Type header
24
+ def parse(data, boundary)
25
+ contents = data.match(/\n--#{Regexp.escape(boundary)}--\n/).pre_match rescue ""
26
+ contents.split(/\n--#{Regexp.escape(boundary)}\n/).reject(&:blank?).map do |part|
27
+ headers = Headers.new
28
+ if md = part.match(/\r?\n\r?\n/)
29
+ body = md.post_match
30
+ md.pre_match.split(/\r?\n/).each do |line|
31
+ headers.parse(line)
32
+ end
33
+
34
+ if headers["content-type"] =~ /multipart\/mixed/
35
+ boundary = extract_boundary(headers.to_hash["content-type"].first)
36
+ parse(body, boundary)
37
+ else
38
+ {:headers => headers.to_hash, :body => body}
39
+ end
40
+ end
41
+ end.compact
42
+ end
43
+
44
+ # Extracts the boundary string from a Content-Type header that is a multipart type
45
+ # @param [String] header_string the Content-Type header
46
+ # @return [String] the boundary string separating each part
47
+ def extract_boundary(header_string)
48
+ $1 if header_string =~ /boundary=([A-Za-z0-9\'()+_,-.\/:=?]+)/
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
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 'riak'
15
+
16
+ module Riak
17
+ module Util
18
+ module Translation
19
+ def i18n_scope
20
+ :riak
21
+ end
22
+
23
+ def t(message, options={})
24
+ I18n.t("#{i18n_scope}.#{message}", options)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,113 @@
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 'riak'
15
+
16
+ module Riak
17
+
18
+ # The specification of how to follow links from one object to another in Riak,
19
+ # when using the link-walker resource.
20
+ # Example link-walking operation:
21
+ # GET /raw/artists/REM/albums,_,_/tracks,_,1
22
+ # This operation would have two WalkSpecs:
23
+ # Riak::WalkSpec.new({:bucket => 'albums'})
24
+ # Riak::WalkSpec.new({:bucket => 'tracks', :result => true})
25
+ class WalkSpec
26
+ include Util::Translation
27
+ # @return [String] The bucket followed links should be restricted to. "_" represents all buckets.
28
+ attr_accessor :bucket
29
+
30
+ # @return [String] The "riaktag" or "rel" that followed links should be restricted to. "_" represents all tags.
31
+ attr_accessor :tag
32
+
33
+ # @return [Boolean] Whether objects should be returned from this phase of link walking. Default is false.
34
+ attr_accessor :keep
35
+
36
+ # Normalize a list of walk specs into WalkSpec objects.
37
+ def self.normalize(*params)
38
+ params.flatten!
39
+ specs = []
40
+ while params.length > 0
41
+ param = params.shift
42
+ case param
43
+ when Hash
44
+ specs << new(param)
45
+ when WalkSpec
46
+ specs << param
47
+ else
48
+ if params.length >= 2
49
+ specs << new(param, params.shift, params.shift)
50
+ else
51
+ raise ArgumentError, t("too_few_arguments", :params => params.inspect)
52
+ end
53
+ end
54
+ end
55
+ specs
56
+ end
57
+
58
+ # Creates a walk-spec for use in finding other objects in Riak.
59
+ # @overload initialize(hash)
60
+ # Creates a walk-spec from a hash.
61
+ # @param [Hash] hash options for the walk-spec
62
+ # @option hash [String] :bucket ("_") the bucket the links should point to (default '_' is all)
63
+ # @option hash [String] :tag ("_") the tag to filter links by (default '_' is all)
64
+ # @option hash [Boolean] :keep (false) whether to return results from following this link specification
65
+ # @overload initialize(bucket, tag, keep)
66
+ # Creates a walk-spec from a bucket-tag-result triple.
67
+ # @param [String] bucket the bucket the links should point to (default '_' is all)
68
+ # @param [String] tag the tag to filter links by (default '_' is all)
69
+ # @param [Boolean] keep whether to return results from following this link specification
70
+ # @see {Riak::RObject#walk}
71
+ def initialize(*args)
72
+ args.flatten!
73
+ case args.size
74
+ when 1
75
+ hash = args.first
76
+ raise ArgumentError, t("hash_type", :hash => hash.inspect) unless Hash === hash
77
+ assign(hash[:bucket], hash[:tag], hash[:keep])
78
+ when 3
79
+ assign(*args)
80
+ else
81
+ raise ArgumentError, t("wrong_argument_count_walk_spec")
82
+ end
83
+ end
84
+
85
+ # Converts the walk-spec into the form required by the link-walker resource URL
86
+ def to_s
87
+ "#{@bucket || '_'},#{@tag || '_'},#{@keep ? '1' : '_'}"
88
+ end
89
+
90
+ def ==(other)
91
+ other.is_a?(WalkSpec) && other.bucket == bucket && other.tag == tag && other.keep == keep
92
+ end
93
+
94
+ def ===(other)
95
+ self == other || case other
96
+ when WalkSpec
97
+ other.keep == keep &&
98
+ (bucket == "_" || bucket == other.bucket) &&
99
+ (tag == "_" || tag == other.tag)
100
+ when Link
101
+ (bucket == "_" || bucket == other.url.split("/")[2]) &&
102
+ (tag == "_" || tag == other.rel)
103
+ end
104
+ end
105
+
106
+ private
107
+ def assign(bucket, tag, result)
108
+ @bucket = bucket || "_"
109
+ @tag = tag || "_"
110
+ @keep = result || false
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,48 @@
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
+ require 'riak'
16
+ require 'active_support/all'
17
+ require 'active_model'
18
+ require 'ripple/i18n'
19
+
20
+ # Contains the classes and modules related to the ODM built on top of
21
+ # the basic Riak client.
22
+ module Ripple
23
+ extend ActiveSupport::Autoload
24
+ include ActiveSupport::Configurable
25
+
26
+ autoload :EmbeddedDocument
27
+ autoload :Document
28
+ autoload :PropertyTypeMismatch
29
+ autoload :Translation
30
+
31
+ class << self
32
+ # @return [Riak::Client] The client for the current thread.
33
+ def client
34
+ Thread.current[:ripple_client] ||= Riak::Client.new
35
+ end
36
+
37
+ # Sets the client for the current thread.
38
+ # @param [Riak::Client] value the client
39
+ def client=(value)
40
+ Thread.current[:ripple_client] = value
41
+ end
42
+
43
+ def config=(value)
44
+ self.client = nil
45
+ super
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,96 @@
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
+ # @private
16
+ class Object
17
+ def self.ripple_cast(value)
18
+ value
19
+ end
20
+ end
21
+
22
+ # @private
23
+ class Symbol
24
+ def self.ripple_cast(value)
25
+ return nil if value.nil?
26
+ value.respond_to?(:to_s) && value.to_s.intern or raise Ripple::PropertyTypeMismatch.new(self, value)
27
+ end
28
+ end
29
+
30
+ # @private
31
+ class Numeric
32
+ def self.ripple_cast(value)
33
+ return nil if value.nil?
34
+ raise Ripple::PropertyTypeMismatch.new(self,value) unless value.respond_to?(:to_i) && value.respond_to?(:to_f)
35
+ float_value = value.to_f
36
+ int_value = value.to_i
37
+ float_value == int_value ? int_value : float_value
38
+ end
39
+ end
40
+
41
+ # @private
42
+ class Integer
43
+ def self.ripple_cast(value)
44
+ return nil if value.nil?
45
+ value.respond_to?(:to_i) && value.to_i or raise Ripple::PropertyTypeMismatch.new(self, value)
46
+ end
47
+ end
48
+
49
+ # @private
50
+ class Float
51
+ def self.ripple_cast(value)
52
+ return nil if value.nil?
53
+ value.respond_to?(:to_f) && value.to_f or raise Ripple::PropertyTypeMismatch.new(self, value)
54
+ end
55
+ end
56
+
57
+ # @private
58
+ class String
59
+ def self.ripple_cast(value)
60
+ value.respond_to?(:to_s) && value.to_s or raise Ripple::PropertyTypeMismatch.new(self, value)
61
+ end
62
+ end
63
+
64
+ # Stand-in for true/false property types.
65
+ module Boolean
66
+ def self.ripple_cast(value)
67
+ case value
68
+ when NilClass
69
+ nil
70
+ when Numeric
71
+ !value.zero?
72
+ when TrueClass, FalseClass
73
+ value
74
+ when /^\s*t/i
75
+ true
76
+ when /^\s*f/i
77
+ false
78
+ else
79
+ value.present?
80
+ end
81
+ end
82
+ end
83
+
84
+ # @private
85
+ class TrueClass
86
+ def self.ripple_cast(value)
87
+ Boolean.ripple_cast(value)
88
+ end
89
+ end
90
+
91
+ # @private
92
+ class FalseClass
93
+ def self.ripple_cast(value)
94
+ Boolean.ripple_cast(value)
95
+ end
96
+ end
@@ -0,0 +1,60 @@
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
+ # Represents a model stored in Riak, serialized in JSON object (document).
18
+ # Ripple::Document models aim to be fully ActiveModel compatible, with a keen
19
+ # eye toward features that developers expect from ActiveRecord, DataMapper and MongoMapper.
20
+ #
21
+ # Example:
22
+ #
23
+ # class Email
24
+ # include Ripple::Document
25
+ # property :from, String, :presence => true
26
+ # property :to, String, :presence => true
27
+ # property :sent, Time, :default => proc { Time.now }
28
+ # property :body, String
29
+ # end
30
+ #
31
+ # email = Email.find("37458abc752f8413e") # GET /raw/emails/37458abc752f8413e
32
+ # email.from = "someone@nowhere.net"
33
+ # email.save # PUT /raw/emails/37458abc752f8413e
34
+ #
35
+ # reply = Email.new
36
+ # reply.from = "justin@bashoooo.com"
37
+ # reply.to = "sean@geeemail.com"
38
+ # reply.body = "Riak is a good fit for scalable Ruby apps."
39
+ # reply.save # POST /raw/emails (Riak-assigned key)
40
+ #
41
+ module Document
42
+ extend ActiveSupport::Concern
43
+ extend ActiveSupport::Autoload
44
+
45
+ autoload :AttributeMethods
46
+ autoload :BucketAccess
47
+ autoload :Finders
48
+ autoload :Persistence
49
+ autoload :Properties
50
+ autoload :Property, "ripple/document/properties"
51
+ autoload :Validations
52
+
53
+ included do
54
+ extend BucketAccess
55
+ include Persistence
56
+ include EmbeddedDocument
57
+ include Finders
58
+ end
59
+ end
60
+ end