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,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