ripple 0.7.1 → 0.8.0.beta
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.
- data/Rakefile +5 -4
- data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
- data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
- data/lib/rails/generators/ripple_generator.rb +60 -0
- data/lib/ripple.rb +24 -6
- data/lib/ripple/associations.rb +70 -3
- data/lib/ripple/associations/linked.rb +17 -5
- data/lib/ripple/associations/many.rb +5 -4
- data/lib/ripple/associations/many_embedded_proxy.rb +2 -0
- data/lib/ripple/associations/many_linked_proxy.rb +35 -0
- data/lib/ripple/associations/one.rb +4 -0
- data/lib/ripple/associations/one_embedded_proxy.rb +1 -1
- data/lib/ripple/associations/one_linked_proxy.rb +29 -0
- data/lib/ripple/attribute_methods/dirty.rb +2 -2
- data/lib/ripple/core_ext.rb +1 -0
- data/lib/ripple/core_ext/casting.rb +6 -8
- data/lib/ripple/document.rb +1 -0
- data/lib/ripple/document/bucket_access.rb +1 -1
- data/lib/ripple/document/finders.rb +1 -1
- data/lib/ripple/document/persistence.rb +28 -8
- data/lib/ripple/embedded_document.rb +1 -0
- data/lib/ripple/embedded_document/persistence.rb +11 -1
- data/lib/ripple/inspection.rb +26 -0
- data/lib/ripple/locale/en.yml +8 -5
- data/lib/ripple/railtie.rb +2 -12
- data/lib/ripple/validations.rb +5 -0
- data/lib/ripple/validations/associated_validator.rb +1 -2
- data/spec/fixtures/config.yml +6 -1
- data/spec/integration/ripple/associations_spec.rb +21 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +6 -0
- data/spec/ripple/associations/many_linked_proxy_spec.rb +103 -0
- data/spec/ripple/associations/one_embedded_proxy_spec.rb +5 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +76 -0
- data/spec/ripple/associations/proxy_spec.rb +1 -1
- data/spec/ripple/bucket_access_spec.rb +3 -4
- data/spec/ripple/core_ext_spec.rb +11 -0
- data/spec/ripple/embedded_document/persistence_spec.rb +12 -0
- data/spec/ripple/finders_spec.rb +1 -1
- data/spec/ripple/inspection_spec.rb +48 -0
- data/spec/ripple/persistence_spec.rb +52 -6
- data/spec/ripple/properties_spec.rb +6 -0
- data/spec/ripple/ripple_spec.rb +12 -1
- data/spec/support/models/tasks.rb +13 -0
- data/spec/support/models/widget.rb +5 -1
- metadata +43 -18
data/Rakefile
CHANGED
@@ -11,10 +11,10 @@ gemspec = Gem::Specification.new do |gem|
|
|
11
11
|
gem.email = "seancribbs@gmail.com"
|
12
12
|
gem.homepage = "http://seancribbs.github.com/ripple"
|
13
13
|
gem.authors = ["Sean Cribbs"]
|
14
|
-
gem.add_development_dependency "rspec", "~>2.0.0.beta.
|
14
|
+
gem.add_development_dependency "rspec", "~>2.0.0.beta.18"
|
15
15
|
gem.add_dependency "riak-client", version
|
16
|
-
gem.add_dependency "activesupport", "3.0.0.
|
17
|
-
gem.add_dependency "activemodel", "3.0.0.
|
16
|
+
gem.add_dependency "activesupport", "3.0.0.rc2"
|
17
|
+
gem.add_dependency "activemodel", "3.0.0.rc2"
|
18
18
|
|
19
19
|
files = FileList["**/*"]
|
20
20
|
files.exclude /\.DS_Store/
|
@@ -26,6 +26,7 @@ gemspec = Gem::Specification.new do |gem|
|
|
26
26
|
files.exclude '**/*.rej'
|
27
27
|
files.exclude /^pkg/
|
28
28
|
files.exclude 'ripple.gemspec'
|
29
|
+
files.exclude 'Gemfile'
|
29
30
|
|
30
31
|
gem.files = files.to_a
|
31
32
|
|
@@ -48,7 +49,7 @@ end
|
|
48
49
|
|
49
50
|
desc %{Release the gem to RubyGems.org}
|
50
51
|
task :release => :gem do
|
51
|
-
"gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
52
|
+
system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
|
52
53
|
end
|
53
54
|
|
54
55
|
require 'rspec/core'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rails/generators/ripple_generator'
|
2
|
+
|
3
|
+
module Ripple
|
4
|
+
module Generators
|
5
|
+
class ModelGenerator < Base
|
6
|
+
desc 'Creates a ripple model'
|
7
|
+
argument :attributes, :type => :array, :default => [], :banner => 'field:type field:type'
|
8
|
+
class_option :parent, :type => :string, :desc => "The parent class for the generated model"
|
9
|
+
class_option :embedded, :type => :boolean, :desc => "Make an embedded document model.", :default => false
|
10
|
+
class_option :embedded_in, :type => :string, :desc => "Specify the enclosing model for the embedded document. Implies --embedded."
|
11
|
+
check_class_collision
|
12
|
+
|
13
|
+
def create_model_file
|
14
|
+
template 'model.rb', "app/models/#{file_path}.rb"
|
15
|
+
end
|
16
|
+
|
17
|
+
hook_for :test_framework
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class <%= class_name %><%= " < #{options[:parent].classify}" if options[:parent] %>
|
2
|
+
<% unless options[:parent] -%>
|
3
|
+
include Ripple::<%= "Embedded" if options[:embedded] || options[:embedded_in] %>Document
|
4
|
+
<% if options[:embedded_in] -%>embedded_in :<%= options[:embedded_in].underscore %><% end -%>
|
5
|
+
<% end -%>
|
6
|
+
|
7
|
+
<% attributes.reject{|attr| attr.reference?}.each do |attribute| -%>
|
8
|
+
property :<%= attribute.name %>, <%= attribute.type_class %>
|
9
|
+
<% end -%>
|
10
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require "rails/generators/named_base"
|
2
|
+
require "rails/generators/active_model"
|
3
|
+
|
4
|
+
module Ripple
|
5
|
+
module Generators
|
6
|
+
class Base < ::Rails::Generators::NamedBase
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
@_ripple_source_root ||=
|
10
|
+
File.expand_path("../#{base_name}/#{generator_name}/templates", __FILE__)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class ActiveModel < ::Rails::Generators::ActiveModel
|
15
|
+
def self.all(klass)
|
16
|
+
"#{klass}.all"
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.find(klass, params=nil)
|
20
|
+
"#{klass}.find(#{params})"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.build(klass, params=nil)
|
24
|
+
if params
|
25
|
+
"#{klass}.new(#{params})"
|
26
|
+
else
|
27
|
+
"#{klass}.new"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
"#{name}.save"
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_attributes(params=nil)
|
36
|
+
"#{name}.update_attributes(#{params})"
|
37
|
+
end
|
38
|
+
|
39
|
+
def errors
|
40
|
+
"#{name}.errors"
|
41
|
+
end
|
42
|
+
|
43
|
+
def destroy
|
44
|
+
"#{name}.destroy"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
module Rails
|
51
|
+
module Generators
|
52
|
+
class GeneratedAttribute #:nodoc:
|
53
|
+
def type_class
|
54
|
+
return "Time" if type.to_s == "datetime"
|
55
|
+
return "String" if type.to_s == "text"
|
56
|
+
return type.to_s.camelcase
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/ripple.rb
CHANGED
@@ -16,12 +16,12 @@ require 'riak'
|
|
16
16
|
require 'active_support/all'
|
17
17
|
require 'active_model'
|
18
18
|
require 'ripple/i18n'
|
19
|
+
require 'ripple/core_ext'
|
19
20
|
|
20
21
|
# Contains the classes and modules related to the ODM built on top of
|
21
22
|
# the basic Riak client.
|
22
23
|
module Ripple
|
23
24
|
extend ActiveSupport::Autoload
|
24
|
-
include ActiveSupport::Configurable
|
25
25
|
|
26
26
|
# Primary models
|
27
27
|
autoload :EmbeddedDocument
|
@@ -42,10 +42,9 @@ module Ripple
|
|
42
42
|
autoload :PropertyTypeMismatch
|
43
43
|
|
44
44
|
# Utilities
|
45
|
+
autoload :Inspection
|
45
46
|
autoload :Translation
|
46
47
|
|
47
|
-
DEFAULT_CONFIG = {}
|
48
|
-
|
49
48
|
class << self
|
50
49
|
# @return [Riak::Client] The client for the current thread.
|
51
50
|
def client
|
@@ -60,11 +59,30 @@ module Ripple
|
|
60
59
|
|
61
60
|
def config=(hash)
|
62
61
|
self.client = nil
|
63
|
-
|
62
|
+
@config = hash.symbolize_keys
|
63
|
+
end
|
64
|
+
|
65
|
+
def config
|
66
|
+
@config ||= {}
|
64
67
|
end
|
65
68
|
|
66
|
-
def
|
67
|
-
|
69
|
+
def load_configuration(config_file, config_keys = [:ripple])
|
70
|
+
config_file = File.expand_path(config_file)
|
71
|
+
config_hash = YAML.load(ERB.new(File.read(config_file)).result).with_indifferent_access
|
72
|
+
config_keys.each {|k| config_hash = config_hash[k]}
|
73
|
+
self.config = config_hash || {}
|
74
|
+
rescue Errno::ENOENT
|
75
|
+
raise Ripple::MissingConfiguration.new(config_file)
|
76
|
+
end
|
77
|
+
alias_method :load_config, :load_configuration
|
78
|
+
end
|
79
|
+
|
80
|
+
class MissingConfiguration < StandardError
|
81
|
+
include Translation
|
82
|
+
def initialize(file_path)
|
83
|
+
super(t("missing_configuration", :file => file_path))
|
68
84
|
end
|
69
85
|
end
|
70
86
|
end
|
87
|
+
|
88
|
+
require 'ripple/railtie' if defined? Rails::Railtie
|
data/lib/ripple/associations.rb
CHANGED
@@ -22,9 +22,12 @@ module Ripple
|
|
22
22
|
autoload :One
|
23
23
|
autoload :Many
|
24
24
|
autoload :Embedded
|
25
|
+
autoload :Linked
|
25
26
|
autoload :Instantiators
|
26
27
|
autoload :OneEmbeddedProxy
|
27
28
|
autoload :ManyEmbeddedProxy
|
29
|
+
autoload :OneLinkedProxy
|
30
|
+
autoload :ManyLinkedProxy
|
28
31
|
|
29
32
|
module ClassMethods
|
30
33
|
# @private
|
@@ -66,7 +69,7 @@ module Ripple
|
|
66
69
|
value
|
67
70
|
end
|
68
71
|
|
69
|
-
|
72
|
+
unless association.many?
|
70
73
|
define_method("#{name}?") do
|
71
74
|
get_proxy(association).present?
|
72
75
|
end
|
@@ -98,6 +101,7 @@ module Ripple
|
|
98
101
|
end
|
99
102
|
|
100
103
|
class Association
|
104
|
+
include Ripple::Translation
|
101
105
|
attr_reader :type, :name, :options
|
102
106
|
|
103
107
|
# association options :using, :class_name, :class, :extend,
|
@@ -107,6 +111,7 @@ module Ripple
|
|
107
111
|
@type, @name, @options = type, name, options.to_options
|
108
112
|
end
|
109
113
|
|
114
|
+
# @return String The class name of the associated object(s)
|
110
115
|
def class_name
|
111
116
|
@class_name ||= case
|
112
117
|
when @options[:class_name]
|
@@ -120,38 +125,100 @@ module Ripple
|
|
120
125
|
end
|
121
126
|
end
|
122
127
|
|
128
|
+
# @return [Class] The class of the associated object(s)
|
123
129
|
def klass
|
124
130
|
@klass ||= options[:class] || class_name.constantize
|
125
131
|
end
|
126
132
|
|
133
|
+
# @return [true,false] Is the cardinality of the association > 1
|
127
134
|
def many?
|
128
135
|
@type == :many
|
129
136
|
end
|
130
137
|
|
138
|
+
# @return [true,false] Is the cardinality of the association == 1
|
131
139
|
def one?
|
132
140
|
@type == :one
|
133
141
|
end
|
134
142
|
|
143
|
+
# @return [true,false] Is the associated class an EmbeddedDocument
|
135
144
|
def embeddable?
|
136
145
|
klass.embeddable?
|
137
146
|
end
|
138
147
|
|
148
|
+
# TODO: Polymorphic not supported
|
149
|
+
# @return [true,false] Does the association support more than one associated class
|
139
150
|
def polymorphic?
|
140
151
|
false
|
141
152
|
end
|
142
153
|
|
154
|
+
# @return [true,false] Does the association use links
|
155
|
+
def linked?
|
156
|
+
using == :linked
|
157
|
+
end
|
158
|
+
|
159
|
+
# @return [String] the instance variable in the owner where the association will be stored
|
143
160
|
def ivar
|
144
161
|
"@_#{name}"
|
145
162
|
end
|
146
163
|
|
164
|
+
# @return [Class] the association proxy class
|
147
165
|
def proxy_class
|
148
166
|
@proxy_class ||= proxy_class_name.constantize
|
149
167
|
end
|
150
168
|
|
169
|
+
# @return [String] the class name of the association proxy
|
151
170
|
def proxy_class_name
|
152
|
-
|
153
|
-
klass_name = (many? ? 'Many' : 'One') + @using.to_s.camelize + ('Polymorphic' if polymorphic?).to_s + 'Proxy'
|
171
|
+
klass_name = (many? ? 'Many' : 'One') + using.to_s.camelize + ('Polymorphic' if polymorphic?).to_s + 'Proxy'
|
154
172
|
"Ripple::Associations::#{klass_name}"
|
155
173
|
end
|
174
|
+
|
175
|
+
# @return [Proc] a filter proc to be used with Enumerable#select for collecting links that belong to this association (only when #linked? is true)
|
176
|
+
def link_filter
|
177
|
+
linked? ? lambda {|link| link.tag == link_tag } : lambda {|_| false }
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [String,nil] when #linked? is true, the tag for outgoing links
|
181
|
+
def link_tag
|
182
|
+
linked? ? Array(link_spec).first.tag : nil
|
183
|
+
end
|
184
|
+
|
185
|
+
# @return [Riak::WalkSpec] when #linked? is true, a specification for which links to follow to retrieve the associated documents
|
186
|
+
def link_spec
|
187
|
+
# TODO: support transitive linked associations
|
188
|
+
if linked?
|
189
|
+
tag = name.to_s
|
190
|
+
bucket = polymorphic? ? '_' : klass.bucket_name
|
191
|
+
Riak::WalkSpec.new(:tag => tag, :bucket => bucket)
|
192
|
+
else
|
193
|
+
nil
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# @return [Symbol] which method is used for representing the association - currently only supports :embedded and :linked
|
198
|
+
def using
|
199
|
+
@using ||= options[:using] || (embeddable? ? :embedded : :linked)
|
200
|
+
end
|
201
|
+
|
202
|
+
# @raise [ArgumentError] if the value does not match the class of the association
|
203
|
+
def verify_type!(value, owner)
|
204
|
+
unless type_matches?(value)
|
205
|
+
raise ArgumentError.new(t('invalid_association_value',
|
206
|
+
:name => name,
|
207
|
+
:owner => owner.inspect,
|
208
|
+
:klass => polymorphic? ? "<polymorphic>" : klass.name,
|
209
|
+
:value => value.inspect))
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def type_matches?(value)
|
214
|
+
case
|
215
|
+
when polymorphic?
|
216
|
+
one? || Array === value
|
217
|
+
when many?
|
218
|
+
Array === value && value.all? {|d| (embeddable? && Hash === d) || klass === d }
|
219
|
+
when one?
|
220
|
+
value.nil? || (embeddable? && Hash === value) || klass === value
|
221
|
+
end
|
222
|
+
end
|
156
223
|
end
|
157
224
|
end
|
@@ -16,15 +16,27 @@ require 'ripple'
|
|
16
16
|
module Ripple
|
17
17
|
module Associations
|
18
18
|
module Linked
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
def replace(value)
|
20
|
+
@reflection.verify_type!(value, @owner)
|
21
|
+
@owner.robject.links -= links
|
22
|
+
Array(value).compact.each do |doc|
|
23
|
+
doc.save if doc.new?
|
24
|
+
@owner.robject.links << doc.robject.to_link(@reflection.link_tag)
|
25
|
+
end
|
26
|
+
loaded
|
27
|
+
@target = value
|
22
28
|
end
|
23
29
|
|
24
|
-
|
25
|
-
|
30
|
+
protected
|
31
|
+
def links
|
32
|
+
@owner.robject.links.select(&@reflection.link_filter)
|
26
33
|
end
|
27
34
|
|
35
|
+
def robjects
|
36
|
+
@owner.robject.walk(*Array(@reflection.link_spec)).first || []
|
37
|
+
rescue
|
38
|
+
[]
|
39
|
+
end
|
28
40
|
end
|
29
41
|
end
|
30
42
|
end
|
@@ -33,13 +33,14 @@ module Ripple
|
|
33
33
|
@target = []
|
34
34
|
end
|
35
35
|
|
36
|
-
def <<
|
37
|
-
|
36
|
+
def <<(value)
|
37
|
+
raise NotImplementedError
|
38
38
|
end
|
39
|
+
|
39
40
|
alias_method :push, :<<
|
40
|
-
|
41
|
+
alias_method :concat, :<<
|
41
42
|
|
42
|
-
|
43
|
+
protected
|
43
44
|
def instantiate_target(instantiator, attrs={})
|
44
45
|
doc = klass.send(instantiator, attrs)
|
45
46
|
self << doc
|
@@ -21,12 +21,14 @@ module Ripple
|
|
21
21
|
|
22
22
|
def <<(docs)
|
23
23
|
load_target
|
24
|
+
@reflection.verify_type!(Array(docs), @owner)
|
24
25
|
assign_references(docs)
|
25
26
|
@target += Array(docs)
|
26
27
|
self
|
27
28
|
end
|
28
29
|
|
29
30
|
def replace(docs)
|
31
|
+
@reflection.verify_type!(docs, @owner)
|
30
32
|
@_docs = docs.map { |doc| attrs = doc.respond_to?(:attributes_for_persistence) ? doc.attributes_for_persistence : doc }
|
31
33
|
assign_references(docs)
|
32
34
|
reset
|
@@ -0,0 +1,35 @@
|
|
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 Associations
|
18
|
+
class ManyLinkedProxy < Proxy
|
19
|
+
include Many
|
20
|
+
include Linked
|
21
|
+
|
22
|
+
def <<(value)
|
23
|
+
load_target
|
24
|
+
new_target = @target.concat(Array(value))
|
25
|
+
replace new_target
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
def find_target
|
31
|
+
robjects.map {|robj| klass.send(:instantiate, robj) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -20,6 +20,7 @@ module Ripple
|
|
20
20
|
include Embedded
|
21
21
|
|
22
22
|
def replace(doc)
|
23
|
+
@reflection.verify_type!(doc, @owner)
|
23
24
|
@_doc = doc.respond_to?(:attributes_for_persistence) ? doc.attributes_for_persistence : doc
|
24
25
|
assign_references(doc)
|
25
26
|
reset
|
@@ -33,7 +34,6 @@ module Ripple
|
|
33
34
|
assign_references(doc)
|
34
35
|
end
|
35
36
|
end
|
36
|
-
|
37
37
|
end
|
38
38
|
end
|
39
39
|
end
|