ripple 0.7.1 → 0.8.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- 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
|