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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.rdoc +13 -13
  3. data/Rakefile +1 -0
  4. data/config/schema.xml.erb +0 -1
  5. data/config/{solrconfig.xml → solrconfig.xml.erb} +1 -1
  6. data/lib/blankslate.rb +1 -1
  7. data/lib/datastax_rails/associations/collection_proxy.rb +6 -2
  8. data/lib/datastax_rails/attribute_assignment.rb +114 -0
  9. data/lib/datastax_rails/attribute_methods/definition.rb +8 -2
  10. data/lib/datastax_rails/attribute_methods/typecasting.rb +2 -5
  11. data/lib/datastax_rails/attribute_methods.rb +9 -7
  12. data/lib/datastax_rails/base.rb +127 -109
  13. data/lib/datastax_rails/callbacks.rb +11 -7
  14. data/lib/datastax_rails/cassandra_only_model.rb +27 -0
  15. data/lib/datastax_rails/collection.rb +3 -1
  16. data/lib/datastax_rails/cql/base.rb +4 -0
  17. data/lib/datastax_rails/cql/select.rb +12 -2
  18. data/lib/datastax_rails/identity/abstract_key_factory.rb +1 -0
  19. data/lib/datastax_rails/identity/custom_key_factory.rb +1 -0
  20. data/lib/datastax_rails/identity/natural_key_factory.rb +1 -0
  21. data/lib/datastax_rails/identity/uuid_key_factory.rb +4 -0
  22. data/lib/datastax_rails/identity.rb +2 -1
  23. data/lib/datastax_rails/inheritance.rb +61 -0
  24. data/lib/datastax_rails/payload_model.rb +2 -5
  25. data/lib/datastax_rails/persistence.rb +63 -20
  26. data/lib/datastax_rails/railtie.rb +5 -1
  27. data/lib/datastax_rails/relation/batches.rb +2 -2
  28. data/lib/datastax_rails/relation/facet_methods.rb +56 -5
  29. data/lib/datastax_rails/relation/finder_methods.rb +55 -1
  30. data/lib/datastax_rails/relation/search_methods.rb +103 -32
  31. data/lib/datastax_rails/relation/spawn_methods.rb +3 -1
  32. data/lib/datastax_rails/relation/stats_methods.rb +1 -1
  33. data/lib/datastax_rails/relation.rb +166 -30
  34. data/lib/datastax_rails/schema/cassandra.rb +165 -0
  35. data/lib/datastax_rails/schema/migrator.rb +85 -193
  36. data/lib/datastax_rails/schema/solr.rb +158 -0
  37. data/lib/datastax_rails/schema.rb +2 -30
  38. data/lib/datastax_rails/scoping/default.rb +142 -0
  39. data/lib/datastax_rails/scoping/named.rb +200 -0
  40. data/lib/datastax_rails/scoping.rb +106 -349
  41. data/lib/datastax_rails/tasks/ds.rake +41 -42
  42. data/lib/datastax_rails/types/array_type.rb +1 -1
  43. data/lib/datastax_rails/types/base_type.rb +2 -2
  44. data/lib/datastax_rails/types/binary_type.rb +1 -1
  45. data/lib/datastax_rails/types/boolean_type.rb +1 -1
  46. data/lib/datastax_rails/types/date_type.rb +1 -1
  47. data/lib/datastax_rails/types/float_type.rb +4 -4
  48. data/lib/datastax_rails/types/integer_type.rb +3 -3
  49. data/lib/datastax_rails/types/string_type.rb +1 -1
  50. data/lib/datastax_rails/types/text_type.rb +1 -1
  51. data/lib/datastax_rails/types/time_type.rb +3 -3
  52. data/lib/datastax_rails/validations/uniqueness.rb +1 -1
  53. data/lib/datastax_rails/version.rb +1 -1
  54. data/lib/datastax_rails/wide_storage_model.rb +44 -0
  55. data/lib/datastax_rails.rb +16 -18
  56. data/spec/datastax_rails/associations_spec.rb +7 -3
  57. data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
  58. data/spec/datastax_rails/base_spec.rb +1 -6
  59. data/spec/datastax_rails/inheritance_spec.rb +41 -0
  60. data/spec/datastax_rails/persistence_spec.rb +13 -3
  61. data/spec/datastax_rails/relation/batches_spec.rb +1 -1
  62. data/spec/datastax_rails/relation/facet_methods_spec.rb +52 -0
  63. data/spec/datastax_rails/relation/finder_methods_spec.rb +22 -1
  64. data/spec/datastax_rails/relation/search_methods_spec.rb +51 -1
  65. data/spec/datastax_rails/relation_spec.rb +14 -3
  66. data/spec/datastax_rails/schema/migrator_spec.rb +92 -0
  67. data/spec/datastax_rails/schema/solr_spec.rb +34 -0
  68. data/spec/datastax_rails/scoping/default_spec.rb +17 -0
  69. data/spec/datastax_rails/types/float_type_spec.rb +5 -9
  70. data/spec/datastax_rails/types/integer_type_spec.rb +5 -9
  71. data/spec/datastax_rails/types/time_type_spec.rb +28 -0
  72. data/spec/datastax_rails/validations/uniqueness_spec.rb +3 -1
  73. data/spec/dummy/config/application.rb +1 -4
  74. data/spec/dummy/config/datastax.yml +1 -1
  75. data/spec/dummy/config/environments/test.rb +2 -0
  76. data/spec/dummy/config/solr/articles-schema.xml.erb +1 -0
  77. data/spec/dummy/db/test.sqlite3 +0 -0
  78. data/spec/dummy/log/development.log +2 -0
  79. data/spec/dummy/log/production.log +2 -0
  80. data/spec/dummy/log/test.log +523 -0
  81. data/spec/spec_helper.rb +11 -0
  82. data/spec/support/models.rb +14 -0
  83. metadata +66 -22
  84. data/lib/datastax_rails/log_subscriber.rb +0 -37
  85. data/lib/datastax_rails/migrations/migration.rb +0 -15
  86. data/lib/datastax_rails/migrations.rb +0 -36
  87. data/lib/datastax_rails/mocking.rb +0 -15
  88. data/lib/datastax_rails/schema/migration.rb +0 -106
  89. data/lib/datastax_rails/schema/migration_proxy.rb +0 -25
  90. data/lib/datastax_rails/tasks/column_family.rb +0 -329
  91. data/lib/datastax_rails/tasks/keyspace.rb +0 -57
  92. 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