datastax_rails 1.1.0.3 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.rdoc +13 -13
- data/Rakefile +1 -0
- data/config/schema.xml.erb +0 -1
- data/config/{solrconfig.xml → solrconfig.xml.erb} +1 -1
- data/lib/blankslate.rb +1 -1
- data/lib/datastax_rails/associations/collection_proxy.rb +6 -2
- data/lib/datastax_rails/attribute_assignment.rb +114 -0
- data/lib/datastax_rails/attribute_methods/definition.rb +8 -2
- data/lib/datastax_rails/attribute_methods/typecasting.rb +2 -5
- data/lib/datastax_rails/attribute_methods.rb +9 -7
- data/lib/datastax_rails/base.rb +127 -109
- data/lib/datastax_rails/callbacks.rb +11 -7
- data/lib/datastax_rails/cassandra_only_model.rb +27 -0
- data/lib/datastax_rails/collection.rb +3 -1
- data/lib/datastax_rails/cql/base.rb +4 -0
- data/lib/datastax_rails/cql/select.rb +12 -2
- data/lib/datastax_rails/identity/abstract_key_factory.rb +1 -0
- data/lib/datastax_rails/identity/custom_key_factory.rb +1 -0
- data/lib/datastax_rails/identity/natural_key_factory.rb +1 -0
- data/lib/datastax_rails/identity/uuid_key_factory.rb +4 -0
- data/lib/datastax_rails/identity.rb +2 -1
- data/lib/datastax_rails/inheritance.rb +61 -0
- data/lib/datastax_rails/payload_model.rb +2 -5
- data/lib/datastax_rails/persistence.rb +63 -20
- data/lib/datastax_rails/railtie.rb +5 -1
- data/lib/datastax_rails/relation/batches.rb +2 -2
- data/lib/datastax_rails/relation/facet_methods.rb +56 -5
- data/lib/datastax_rails/relation/finder_methods.rb +55 -1
- data/lib/datastax_rails/relation/search_methods.rb +103 -32
- data/lib/datastax_rails/relation/spawn_methods.rb +3 -1
- data/lib/datastax_rails/relation/stats_methods.rb +1 -1
- data/lib/datastax_rails/relation.rb +166 -30
- data/lib/datastax_rails/schema/cassandra.rb +165 -0
- data/lib/datastax_rails/schema/migrator.rb +85 -193
- data/lib/datastax_rails/schema/solr.rb +158 -0
- data/lib/datastax_rails/schema.rb +2 -30
- data/lib/datastax_rails/scoping/default.rb +142 -0
- data/lib/datastax_rails/scoping/named.rb +200 -0
- data/lib/datastax_rails/scoping.rb +106 -349
- data/lib/datastax_rails/tasks/ds.rake +41 -42
- data/lib/datastax_rails/types/array_type.rb +1 -1
- data/lib/datastax_rails/types/base_type.rb +2 -2
- data/lib/datastax_rails/types/binary_type.rb +1 -1
- data/lib/datastax_rails/types/boolean_type.rb +1 -1
- data/lib/datastax_rails/types/date_type.rb +1 -1
- data/lib/datastax_rails/types/float_type.rb +4 -4
- data/lib/datastax_rails/types/integer_type.rb +3 -3
- data/lib/datastax_rails/types/string_type.rb +1 -1
- data/lib/datastax_rails/types/text_type.rb +1 -1
- data/lib/datastax_rails/types/time_type.rb +3 -3
- data/lib/datastax_rails/validations/uniqueness.rb +1 -1
- data/lib/datastax_rails/version.rb +1 -1
- data/lib/datastax_rails/wide_storage_model.rb +44 -0
- data/lib/datastax_rails.rb +16 -18
- data/spec/datastax_rails/associations_spec.rb +7 -3
- data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
- data/spec/datastax_rails/base_spec.rb +1 -6
- data/spec/datastax_rails/inheritance_spec.rb +41 -0
- data/spec/datastax_rails/persistence_spec.rb +13 -3
- data/spec/datastax_rails/relation/batches_spec.rb +1 -1
- data/spec/datastax_rails/relation/facet_methods_spec.rb +52 -0
- data/spec/datastax_rails/relation/finder_methods_spec.rb +22 -1
- data/spec/datastax_rails/relation/search_methods_spec.rb +51 -1
- data/spec/datastax_rails/relation_spec.rb +14 -3
- data/spec/datastax_rails/schema/migrator_spec.rb +92 -0
- data/spec/datastax_rails/schema/solr_spec.rb +34 -0
- data/spec/datastax_rails/scoping/default_spec.rb +17 -0
- data/spec/datastax_rails/types/float_type_spec.rb +5 -9
- data/spec/datastax_rails/types/integer_type_spec.rb +5 -9
- data/spec/datastax_rails/types/time_type_spec.rb +28 -0
- data/spec/datastax_rails/validations/uniqueness_spec.rb +3 -1
- data/spec/dummy/config/application.rb +1 -4
- data/spec/dummy/config/datastax.yml +1 -1
- data/spec/dummy/config/environments/test.rb +2 -0
- data/spec/dummy/config/solr/articles-schema.xml.erb +1 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +2 -0
- data/spec/dummy/log/production.log +2 -0
- data/spec/dummy/log/test.log +523 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/support/models.rb +14 -0
- metadata +66 -22
- data/lib/datastax_rails/log_subscriber.rb +0 -37
- data/lib/datastax_rails/migrations/migration.rb +0 -15
- data/lib/datastax_rails/migrations.rb +0 -36
- data/lib/datastax_rails/mocking.rb +0 -15
- data/lib/datastax_rails/schema/migration.rb +0 -106
- data/lib/datastax_rails/schema/migration_proxy.rb +0 -25
- data/lib/datastax_rails/tasks/column_family.rb +0 -329
- data/lib/datastax_rails/tasks/keyspace.rb +0 -57
- data/spec/support/connection_double.rb +0 -6
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module DatastaxRails
|
4
|
+
module Scoping
|
5
|
+
module Default
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Stores the default scope for the class
|
10
|
+
class_attribute :default_scopes, :instance_writer => false
|
11
|
+
self.default_scopes = []
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Returns a scope for the model without the default_scope.
|
16
|
+
#
|
17
|
+
# class Post < DatastaxRails::Base
|
18
|
+
# def self.default_scope
|
19
|
+
# where :published => true
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
24
|
+
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
25
|
+
#
|
26
|
+
# This method also accepts a block. All queries inside the block will
|
27
|
+
# not use the default_scope:
|
28
|
+
#
|
29
|
+
# Post.unscoped {
|
30
|
+
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# It is recommended to use the block form of unscoped because chaining
|
34
|
+
# unscoped with <tt>scope</tt> does not work. Assuming that
|
35
|
+
# <tt>published</tt> is a <tt>scope</tt>, the following two statements
|
36
|
+
# are equal: the default_scope is applied on both.
|
37
|
+
#
|
38
|
+
# Post.unscoped.published
|
39
|
+
# Post.published
|
40
|
+
def unscoped #:nodoc:
|
41
|
+
block_given? ? relation.scoping { yield } : relation
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_remove_const #:nodoc:
|
45
|
+
self.current_scope = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
# Use this macro in your model to set a default scope for all operations on
|
51
|
+
# the model.
|
52
|
+
#
|
53
|
+
# class Article < DatastaxRails::Base
|
54
|
+
# default_scope where(:published => true)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Article.all # => SELECT * FROM articles WHERE published = true
|
58
|
+
#
|
59
|
+
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
60
|
+
# applied while updating a record.
|
61
|
+
#
|
62
|
+
# Article.new.published # => true
|
63
|
+
# Article.create.published # => true
|
64
|
+
#
|
65
|
+
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
66
|
+
#
|
67
|
+
# class Article < DatastaxRails::Base
|
68
|
+
# default_scope { where(:published_at => Time.now - 1.week) }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
72
|
+
# macro, and it will be called when building the default scope.)
|
73
|
+
#
|
74
|
+
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
75
|
+
# be merged together:
|
76
|
+
#
|
77
|
+
# class Article < DatastaxRails::Base
|
78
|
+
# default_scope where(:published => true)
|
79
|
+
# default_scope where(:rating => 'G')
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
83
|
+
#
|
84
|
+
# This is also the case with inheritance and module includes where the parent or module
|
85
|
+
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
86
|
+
#
|
87
|
+
# If you need to do more complex things with a default scope, you can alternatively
|
88
|
+
# define it as a class method:
|
89
|
+
#
|
90
|
+
# class Article < DatastaxRails::Base
|
91
|
+
# def self.default_scope
|
92
|
+
# # Should return a scope, you can call 'super' here etc.
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
def default_scope(scope = {})
|
96
|
+
scope = Proc.new if block_given?
|
97
|
+
self.default_scopes = default_scopes + [scope]
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_default_scope #:nodoc:
|
101
|
+
if method(:default_scope).owner != DatastaxRails::Scoping::Default::ClassMethods
|
102
|
+
evaluate_default_scope { default_scope }
|
103
|
+
elsif default_scopes.any?
|
104
|
+
evaluate_default_scope do
|
105
|
+
default_scopes.inject(relation) do |default_scope, scope|
|
106
|
+
if scope.is_a?(Hash)
|
107
|
+
default_scope.apply_finder_options(scope)
|
108
|
+
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
109
|
+
default_scope.merge(scope.call)
|
110
|
+
else
|
111
|
+
default_scope.merge(scope)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def ignore_default_scope? #:nodoc:
|
119
|
+
Thread.current["#{self}_ignore_default_scope"]
|
120
|
+
end
|
121
|
+
|
122
|
+
def ignore_default_scope=(ignore) #:nodoc:
|
123
|
+
Thread.current["#{self}_ignore_default_scope"] = ignore
|
124
|
+
end
|
125
|
+
|
126
|
+
# The ignore_default_scope flag is used to prevent an infinite recursion situation where
|
127
|
+
# a default scope references a scope which has a default scope which references a scope...
|
128
|
+
def evaluate_default_scope
|
129
|
+
return if ignore_default_scope?
|
130
|
+
|
131
|
+
begin
|
132
|
+
self.ignore_default_scope = true
|
133
|
+
yield
|
134
|
+
ensure
|
135
|
+
self.ignore_default_scope = false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,200 @@
|
|
1
|
+
require 'active_support/core_ext/array'
|
2
|
+
require 'active_support/core_ext/hash/except'
|
3
|
+
require 'active_support/core_ext/kernel/singleton_class'
|
4
|
+
require 'active_support/core_ext/object/blank'
|
5
|
+
require 'active_support/core_ext/class/attribute'
|
6
|
+
|
7
|
+
module DatastaxRails
|
8
|
+
# = DatastaxRails Named \Scopes
|
9
|
+
module Scoping
|
10
|
+
module Named
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
# Returns an anonymous \scope.
|
15
|
+
#
|
16
|
+
# posts = Post.scoped
|
17
|
+
# posts.size # Fires "select count(*) from posts" and returns the count
|
18
|
+
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
19
|
+
#
|
20
|
+
# fruits = Fruit.scoped
|
21
|
+
# fruits = fruits.where(:color => 'red') if options[:red_only]
|
22
|
+
# fruits = fruits.limit(10) if limited?
|
23
|
+
#
|
24
|
+
# Anonymous \scopes tend to be useful when procedurally generating complex
|
25
|
+
# queries, where passing intermediate values (\scopes) around as first-class
|
26
|
+
# objects is convenient.
|
27
|
+
#
|
28
|
+
# You can define a \scope that applies to all finders using
|
29
|
+
# DatastaxRails::Base.default_scope.
|
30
|
+
def scoped(options = nil)
|
31
|
+
if options
|
32
|
+
scoped.apply_finder_options(options)
|
33
|
+
else
|
34
|
+
if current_scope
|
35
|
+
current_scope.clone
|
36
|
+
else
|
37
|
+
scope = relation
|
38
|
+
scope.default_scoped = true
|
39
|
+
scope
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
##
|
45
|
+
# Collects attributes from scopes that should be applied when creating
|
46
|
+
# a DSR instance for the particular class this is called on.
|
47
|
+
def scope_attributes # :nodoc:
|
48
|
+
if current_scope
|
49
|
+
current_scope.scope_for_create
|
50
|
+
else
|
51
|
+
scope = relation
|
52
|
+
scope.default_scoped = true
|
53
|
+
scope.scope_for_create
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Are there default attributes associated with this scope?
|
59
|
+
def scope_attributes? # :nodoc:
|
60
|
+
current_scope || default_scopes.any?
|
61
|
+
end
|
62
|
+
|
63
|
+
# Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a database query,
|
64
|
+
# such as <tt>where(:color => :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
65
|
+
#
|
66
|
+
# class Shirt < DatastaxRails::Base
|
67
|
+
# scope :red, where(:color => 'red')
|
68
|
+
# scope :dry_clean_only, joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true)
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
|
72
|
+
# in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
|
73
|
+
#
|
74
|
+
# Note that this is simply 'syntactic sugar' for defining an actual class method:
|
75
|
+
#
|
76
|
+
# class Shirt < DatastaxRails::Base
|
77
|
+
# def self.red
|
78
|
+
# where(:color => 'red')
|
79
|
+
# end
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
|
83
|
+
# resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
|
84
|
+
# you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
|
85
|
+
# Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
|
86
|
+
# <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
|
87
|
+
# all behave as if Shirt.red really was an Array.
|
88
|
+
#
|
89
|
+
# These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
|
90
|
+
# all shirts that are both red and dry clean only.
|
91
|
+
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
|
92
|
+
# returns the number of garments for which these criteria obtain. Similarly with
|
93
|
+
# <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
94
|
+
#
|
95
|
+
# All \scopes are available as class methods on the DatastaxRails::Base descendant upon which
|
96
|
+
# the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
|
97
|
+
#
|
98
|
+
# class Person < DatastaxRails::Base
|
99
|
+
# has_many :shirts
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
|
103
|
+
# only shirts.
|
104
|
+
#
|
105
|
+
# Named \scopes can also be procedural:
|
106
|
+
#
|
107
|
+
# class Shirt < DatastaxRails::Base
|
108
|
+
# scope :colored, lambda { |color| where(:color => color) }
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
112
|
+
#
|
113
|
+
# On Ruby 1.9 you can use the 'stabby lambda' syntax:
|
114
|
+
#
|
115
|
+
# scope :colored, ->(color) { where(:color => color) }
|
116
|
+
#
|
117
|
+
# Note that scopes defined with \scope will be evaluated when they are defined, rather than
|
118
|
+
# when they are used. For example, the following would be incorrect:
|
119
|
+
#
|
120
|
+
# class Post < DatastaxRails::Base
|
121
|
+
# scope :recent, where('published_at >= ?', Time.current - 1.week)
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# The example above would be 'frozen' to the <tt>Time.current</tt> value when the <tt>Post</tt>
|
125
|
+
# class was defined, and so the resultant SQL query would always be the same. The correct
|
126
|
+
# way to do this would be via a lambda, which will re-evaluate the scope each time
|
127
|
+
# it is called:
|
128
|
+
#
|
129
|
+
# class Post < DatastaxRails::Base
|
130
|
+
# scope :recent, lambda { where('published_at >= ?', Time.current - 1.week) }
|
131
|
+
# end
|
132
|
+
#
|
133
|
+
# Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
134
|
+
#
|
135
|
+
# class Shirt < DatastaxRails::Base
|
136
|
+
# scope :red, where(:color => 'red') do
|
137
|
+
# def dom_id
|
138
|
+
# 'red_shirts'
|
139
|
+
# end
|
140
|
+
# end
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# Scopes can also be used while creating/building a record.
|
144
|
+
#
|
145
|
+
# class Article < DatastaxRails::Base
|
146
|
+
# scope :published, where(:published => true)
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# Article.published.new.published # => true
|
150
|
+
# Article.published.create.published # => true
|
151
|
+
#
|
152
|
+
# Class methods on your model are automatically available
|
153
|
+
# on scopes. Assuming the following setup:
|
154
|
+
#
|
155
|
+
# class Article < DatastaxRails::Base
|
156
|
+
# scope :published, where(:published => true)
|
157
|
+
# scope :featured, where(:featured => true)
|
158
|
+
#
|
159
|
+
# def self.latest_article
|
160
|
+
# order('published_at desc').first
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# def self.titles
|
164
|
+
# pluck(:title)
|
165
|
+
# end
|
166
|
+
# end
|
167
|
+
#
|
168
|
+
# We are able to call the methods like this:
|
169
|
+
#
|
170
|
+
# Article.published.featured.latest_article
|
171
|
+
# Article.featured.titles
|
172
|
+
def scope(name, scope_options = {})
|
173
|
+
name = name.to_sym
|
174
|
+
valid_scope_name?(name)
|
175
|
+
extension = Module.new(&Proc.new) if block_given?
|
176
|
+
|
177
|
+
scope_proc = lambda do |*args|
|
178
|
+
options = scope_options.respond_to?(:call) ? unscoped { scope_options.call(*args) } : scope_options
|
179
|
+
options = scoped.apply_finder_options(options) if options.is_a?(Hash)
|
180
|
+
|
181
|
+
relation = scoped.merge(options)
|
182
|
+
|
183
|
+
extension ? relation.extending(extension) : relation
|
184
|
+
end
|
185
|
+
|
186
|
+
singleton_class.send(:redefine_method, name, &scope_proc)
|
187
|
+
end
|
188
|
+
|
189
|
+
protected
|
190
|
+
|
191
|
+
def valid_scope_name?(name)
|
192
|
+
if logger && respond_to?(name, true)
|
193
|
+
logger.warn "Creating scope :#{name}. " \
|
194
|
+
"Overwriting existing method #{self.name}.#{name}."
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|