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.
Files changed (45) hide show
  1. data/Rakefile +5 -4
  2. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  3. data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
  4. data/lib/rails/generators/ripple_generator.rb +60 -0
  5. data/lib/ripple.rb +24 -6
  6. data/lib/ripple/associations.rb +70 -3
  7. data/lib/ripple/associations/linked.rb +17 -5
  8. data/lib/ripple/associations/many.rb +5 -4
  9. data/lib/ripple/associations/many_embedded_proxy.rb +2 -0
  10. data/lib/ripple/associations/many_linked_proxy.rb +35 -0
  11. data/lib/ripple/associations/one.rb +4 -0
  12. data/lib/ripple/associations/one_embedded_proxy.rb +1 -1
  13. data/lib/ripple/associations/one_linked_proxy.rb +29 -0
  14. data/lib/ripple/attribute_methods/dirty.rb +2 -2
  15. data/lib/ripple/core_ext.rb +1 -0
  16. data/lib/ripple/core_ext/casting.rb +6 -8
  17. data/lib/ripple/document.rb +1 -0
  18. data/lib/ripple/document/bucket_access.rb +1 -1
  19. data/lib/ripple/document/finders.rb +1 -1
  20. data/lib/ripple/document/persistence.rb +28 -8
  21. data/lib/ripple/embedded_document.rb +1 -0
  22. data/lib/ripple/embedded_document/persistence.rb +11 -1
  23. data/lib/ripple/inspection.rb +26 -0
  24. data/lib/ripple/locale/en.yml +8 -5
  25. data/lib/ripple/railtie.rb +2 -12
  26. data/lib/ripple/validations.rb +5 -0
  27. data/lib/ripple/validations/associated_validator.rb +1 -2
  28. data/spec/fixtures/config.yml +6 -1
  29. data/spec/integration/ripple/associations_spec.rb +21 -0
  30. data/spec/ripple/associations/many_embedded_proxy_spec.rb +6 -0
  31. data/spec/ripple/associations/many_linked_proxy_spec.rb +103 -0
  32. data/spec/ripple/associations/one_embedded_proxy_spec.rb +5 -0
  33. data/spec/ripple/associations/one_linked_proxy_spec.rb +76 -0
  34. data/spec/ripple/associations/proxy_spec.rb +1 -1
  35. data/spec/ripple/bucket_access_spec.rb +3 -4
  36. data/spec/ripple/core_ext_spec.rb +11 -0
  37. data/spec/ripple/embedded_document/persistence_spec.rb +12 -0
  38. data/spec/ripple/finders_spec.rb +1 -1
  39. data/spec/ripple/inspection_spec.rb +48 -0
  40. data/spec/ripple/persistence_spec.rb +52 -6
  41. data/spec/ripple/properties_spec.rb +6 -0
  42. data/spec/ripple/ripple_spec.rb +12 -1
  43. data/spec/support/models/tasks.rb +13 -0
  44. data/spec/support/models/widget.rb +5 -1
  45. 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.6"
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.beta3"
17
- gem.add_dependency "activemodel", "3.0.0.beta3"
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
- super
62
+ @config = hash.symbolize_keys
63
+ end
64
+
65
+ def config
66
+ @config ||= {}
64
67
  end
65
68
 
66
- def load_config(config_file)
67
- self.config = YAML.load_file(File.expand_path config_file).with_indifferent_access[:ripple]
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
@@ -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
- if association.one?
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
- @using ||= options[:using] || (embeddable? ? :embedded : :link)
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
- def create(attrs={})
21
- instantiate_target(:create, attrs)
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
- def create!(attrs={})
25
- instantiate_target(:create!, attrs)
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
- raise NotImplementedError
36
+ def <<(value)
37
+ raise NotImplementedError
38
38
  end
39
+
39
40
  alias_method :push, :<<
40
- alias_method :concat, :<<
41
+ alias_method :concat, :<<
41
42
 
42
- protected
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
@@ -18,6 +18,10 @@ module Ripple
18
18
  module One
19
19
  include Instantiators
20
20
 
21
+ def to_a
22
+ [self]
23
+ end
24
+
21
25
  protected
22
26
  def instantiate_target(instantiator, attrs={})
23
27
  @target = klass.send(instantiator, attrs)
@@ -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