datastax_rails 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +62 -0
  3. data/Rakefile +34 -0
  4. data/config/schema.xml +266 -0
  5. data/config/schema.xml.erb +70 -0
  6. data/config/solrconfig.xml +1564 -0
  7. data/config/stopwords.txt +58 -0
  8. data/lib/datastax_rails/associations/association.rb +224 -0
  9. data/lib/datastax_rails/associations/association_scope.rb +25 -0
  10. data/lib/datastax_rails/associations/belongs_to_association.rb +64 -0
  11. data/lib/datastax_rails/associations/builder/association.rb +56 -0
  12. data/lib/datastax_rails/associations/builder/belongs_to.rb +30 -0
  13. data/lib/datastax_rails/associations/builder/collection_association.rb +48 -0
  14. data/lib/datastax_rails/associations/builder/has_and_belongs_to_many.rb +36 -0
  15. data/lib/datastax_rails/associations/builder/has_many.rb +54 -0
  16. data/lib/datastax_rails/associations/builder/has_one.rb +52 -0
  17. data/lib/datastax_rails/associations/builder/singular_association.rb +56 -0
  18. data/lib/datastax_rails/associations/collection_association.rb +274 -0
  19. data/lib/datastax_rails/associations/collection_proxy.rb +118 -0
  20. data/lib/datastax_rails/associations/has_and_belongs_to_many_association.rb +44 -0
  21. data/lib/datastax_rails/associations/has_many_association.rb +58 -0
  22. data/lib/datastax_rails/associations/has_one_association.rb +68 -0
  23. data/lib/datastax_rails/associations/singular_association.rb +58 -0
  24. data/lib/datastax_rails/associations.rb +86 -0
  25. data/lib/datastax_rails/attribute_methods/definition.rb +20 -0
  26. data/lib/datastax_rails/attribute_methods/dirty.rb +43 -0
  27. data/lib/datastax_rails/attribute_methods/typecasting.rb +50 -0
  28. data/lib/datastax_rails/attribute_methods.rb +104 -0
  29. data/lib/datastax_rails/base.rb +587 -0
  30. data/lib/datastax_rails/batches.rb +35 -0
  31. data/lib/datastax_rails/callbacks.rb +37 -0
  32. data/lib/datastax_rails/collection.rb +9 -0
  33. data/lib/datastax_rails/connection.rb +21 -0
  34. data/lib/datastax_rails/consistency.rb +33 -0
  35. data/lib/datastax_rails/cql/base.rb +15 -0
  36. data/lib/datastax_rails/cql/column_family.rb +38 -0
  37. data/lib/datastax_rails/cql/consistency.rb +13 -0
  38. data/lib/datastax_rails/cql/create_column_family.rb +63 -0
  39. data/lib/datastax_rails/cql/create_keyspace.rb +30 -0
  40. data/lib/datastax_rails/cql/delete.rb +41 -0
  41. data/lib/datastax_rails/cql/drop_column_family.rb +13 -0
  42. data/lib/datastax_rails/cql/drop_keyspace.rb +13 -0
  43. data/lib/datastax_rails/cql/insert.rb +53 -0
  44. data/lib/datastax_rails/cql/select.rb +51 -0
  45. data/lib/datastax_rails/cql/truncate.rb +13 -0
  46. data/lib/datastax_rails/cql/update.rb +68 -0
  47. data/lib/datastax_rails/cql/use_keyspace.rb +13 -0
  48. data/lib/datastax_rails/cql.rb +25 -0
  49. data/lib/datastax_rails/cursor.rb +90 -0
  50. data/lib/datastax_rails/errors.rb +16 -0
  51. data/lib/datastax_rails/identity/abstract_key_factory.rb +26 -0
  52. data/lib/datastax_rails/identity/custom_key_factory.rb +36 -0
  53. data/lib/datastax_rails/identity/hashed_natural_key_factory.rb +10 -0
  54. data/lib/datastax_rails/identity/natural_key_factory.rb +37 -0
  55. data/lib/datastax_rails/identity/uuid_key_factory.rb +23 -0
  56. data/lib/datastax_rails/identity.rb +53 -0
  57. data/lib/datastax_rails/log_subscriber.rb +37 -0
  58. data/lib/datastax_rails/migrations/migration.rb +15 -0
  59. data/lib/datastax_rails/migrations.rb +36 -0
  60. data/lib/datastax_rails/mocking.rb +15 -0
  61. data/lib/datastax_rails/persistence.rb +133 -0
  62. data/lib/datastax_rails/railtie.rb +20 -0
  63. data/lib/datastax_rails/reflection.rb +472 -0
  64. data/lib/datastax_rails/relation/finder_methods.rb +184 -0
  65. data/lib/datastax_rails/relation/modification_methods.rb +80 -0
  66. data/lib/datastax_rails/relation/search_methods.rb +349 -0
  67. data/lib/datastax_rails/relation/spawn_methods.rb +107 -0
  68. data/lib/datastax_rails/relation.rb +393 -0
  69. data/lib/datastax_rails/schema/migration.rb +106 -0
  70. data/lib/datastax_rails/schema/migration_proxy.rb +25 -0
  71. data/lib/datastax_rails/schema/migrator.rb +212 -0
  72. data/lib/datastax_rails/schema.rb +37 -0
  73. data/lib/datastax_rails/scoping.rb +394 -0
  74. data/lib/datastax_rails/serialization.rb +6 -0
  75. data/lib/datastax_rails/tasks/column_family.rb +162 -0
  76. data/lib/datastax_rails/tasks/ds.rake +63 -0
  77. data/lib/datastax_rails/tasks/keyspace.rb +57 -0
  78. data/lib/datastax_rails/timestamps.rb +19 -0
  79. data/lib/datastax_rails/type.rb +16 -0
  80. data/lib/datastax_rails/types/array_type.rb +77 -0
  81. data/lib/datastax_rails/types/base_type.rb +26 -0
  82. data/lib/datastax_rails/types/binary_type.rb +15 -0
  83. data/lib/datastax_rails/types/boolean_type.rb +22 -0
  84. data/lib/datastax_rails/types/date_type.rb +17 -0
  85. data/lib/datastax_rails/types/float_type.rb +18 -0
  86. data/lib/datastax_rails/types/integer_type.rb +18 -0
  87. data/lib/datastax_rails/types/string_type.rb +16 -0
  88. data/lib/datastax_rails/types/text_type.rb +16 -0
  89. data/lib/datastax_rails/types/time_type.rb +17 -0
  90. data/lib/datastax_rails/types.rb +9 -0
  91. data/lib/datastax_rails/validations/uniqueness.rb +119 -0
  92. data/lib/datastax_rails/validations.rb +48 -0
  93. data/lib/datastax_rails/version.rb +3 -0
  94. data/lib/datastax_rails.rb +87 -0
  95. data/lib/solr_no_escape.rb +28 -0
  96. data/spec/datastax_rails/associations/belongs_to_association_spec.rb +7 -0
  97. data/spec/datastax_rails/associations/has_many_association_spec.rb +37 -0
  98. data/spec/datastax_rails/associations_spec.rb +22 -0
  99. data/spec/datastax_rails/attribute_methods_spec.rb +23 -0
  100. data/spec/datastax_rails/base_spec.rb +15 -0
  101. data/spec/datastax_rails/cql/select_spec.rb +12 -0
  102. data/spec/datastax_rails/cql/update_spec.rb +0 -0
  103. data/spec/datastax_rails/relation/finder_methods_spec.rb +54 -0
  104. data/spec/datastax_rails/relation/modification_methods_spec.rb +41 -0
  105. data/spec/datastax_rails/relation/search_methods_spec.rb +117 -0
  106. data/spec/datastax_rails/relation/spawn_methods_spec.rb +28 -0
  107. data/spec/datastax_rails/relation_spec.rb +130 -0
  108. data/spec/datastax_rails/validations/uniqueness_spec.rb +41 -0
  109. data/spec/datastax_rails_spec.rb +5 -0
  110. data/spec/dummy/Rakefile +8 -0
  111. data/spec/dummy/app/assets/javascripts/application.js +9 -0
  112. data/spec/dummy/app/assets/stylesheets/application.css +7 -0
  113. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  114. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  115. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  116. data/spec/dummy/config/application.rb +47 -0
  117. data/spec/dummy/config/boot.rb +10 -0
  118. data/spec/dummy/config/database.yml +25 -0
  119. data/spec/dummy/config/datastax.yml +18 -0
  120. data/spec/dummy/config/environment.rb +5 -0
  121. data/spec/dummy/config/environments/development.rb +30 -0
  122. data/spec/dummy/config/environments/production.rb +60 -0
  123. data/spec/dummy/config/environments/test.rb +39 -0
  124. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  125. data/spec/dummy/config/initializers/inflections.rb +10 -0
  126. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  127. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  128. data/spec/dummy/config/initializers/session_store.rb +8 -0
  129. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  130. data/spec/dummy/config/locales/en.yml +5 -0
  131. data/spec/dummy/config/routes.rb +58 -0
  132. data/spec/dummy/config/sunspot.yml +17 -0
  133. data/spec/dummy/config.ru +4 -0
  134. data/spec/dummy/ks/migrate/20111117224534_models.rb +20 -0
  135. data/spec/dummy/ks/schema.json +180 -0
  136. data/spec/dummy/log/development.log +298 -0
  137. data/spec/dummy/log/production.log +0 -0
  138. data/spec/dummy/log/test.log +20307 -0
  139. data/spec/dummy/public/404.html +26 -0
  140. data/spec/dummy/public/422.html +26 -0
  141. data/spec/dummy/public/500.html +26 -0
  142. data/spec/dummy/public/favicon.ico +0 -0
  143. data/spec/dummy/script/rails +6 -0
  144. data/spec/spec.opts +5 -0
  145. data/spec/spec_helper.rb +29 -0
  146. data/spec/support/datastax_test_hook.rb +14 -0
  147. data/spec/support/models.rb +72 -0
  148. metadata +353 -0
@@ -0,0 +1,212 @@
1
+ module DatastaxRails
2
+ module Schema
3
+
4
+ class Migrator
5
+
6
+ def self.migrate(migrations_path, target_version = nil)
7
+ case
8
+ when target_version.nil?
9
+ up(migrations_path, target_version)
10
+ when current_version == 0 && target_version == 0
11
+ when current_version > target_version
12
+ down(migrations_path, target_version)
13
+ else
14
+ up(migrations_path, target_version)
15
+ end
16
+ end
17
+
18
+ def self.rollback(migrations_path, steps = 1)
19
+ move(:down, migrations_path, steps)
20
+ end
21
+
22
+ def self.forward(migrations_path, steps = 1)
23
+ move(:up, migrations_path, steps)
24
+ end
25
+
26
+ def self.up(migrations_path, target_version = nil)
27
+ new(:up, migrations_path, target_version).migrate
28
+ end
29
+
30
+ def self.down(migrations_path, target_version = nil)
31
+ new(:down, migrations_path, target_version).migrate
32
+ end
33
+
34
+ def self.run(direction, migrations_path, target_version)
35
+ new(direction, migrations_path, target_version).run
36
+ end
37
+
38
+ def self.migrations_path
39
+ 'ks/migrate'
40
+ end
41
+
42
+ def self.schema_migrations_column_family
43
+ :schema_migrations
44
+ end
45
+
46
+ def self.column_family_tasks
47
+ cas = DatastaxRails::Base.connection
48
+ Tasks::ColumnFamily.new(cas.keyspace)
49
+ end
50
+
51
+ def self.get_all_versions
52
+ cas = DatastaxRails::Base.connection
53
+ cas.get(schema_migrations_column_family, 'all').map {|(name, _value)| name.to_i}.sort
54
+ end
55
+
56
+ def self.current_version
57
+ sm_cf = schema_migrations_column_family
58
+ if column_family_tasks.exists?(sm_cf)
59
+ get_all_versions.max || 0
60
+ else
61
+ 0
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def self.move(direction, migrations_path, steps)
68
+ migrator = self.new(direction, migrations_path)
69
+ start_index = migrator.migrations.index(migrator.current_migration)
70
+
71
+ if start_index
72
+ finish = migrator.migrations[start_index + steps]
73
+ version = finish ? finish.version : 0
74
+ send(direction, migrations_path, version)
75
+ end
76
+ end
77
+
78
+ public
79
+
80
+ def initialize(direction, migrations_path, target_version = nil)
81
+ sm_cf = self.class.schema_migrations_column_family
82
+
83
+ unless column_family_tasks.exists?(sm_cf)
84
+ column_family_tasks.create(sm_cf) do |cf|
85
+ cf.comparator_type = 'LongType'
86
+ end
87
+ end
88
+
89
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
90
+ end
91
+
92
+ def current_version
93
+ migrated.last || 0
94
+ end
95
+
96
+ def current_migration
97
+ migrations.detect { |m| m.version == current_version }
98
+ end
99
+
100
+ def run
101
+ target = migrations.detect { |m| m.version == @target_version }
102
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
103
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
104
+ target.migrate(@direction)
105
+ record_version_state_after_migrating(target)
106
+ end
107
+ end
108
+
109
+ def migrate
110
+ current = migrations.detect { |m| m.version == current_version }
111
+ target = migrations.detect { |m| m.version == @target_version }
112
+
113
+ if target.nil? && !@target_version.nil? && @target_version > 0
114
+ raise UnknownMigrationVersionError.new(@target_version)
115
+ end
116
+
117
+ start = up? ? 0 : (migrations.index(current) || 0)
118
+ finish = migrations.index(target) || migrations.size - 1
119
+ runnable = migrations[start..finish]
120
+
121
+ # skip the last migration if we're headed down, but not ALL the way down
122
+ runnable.pop if down? && !target.nil?
123
+
124
+ runnable.each do |migration|
125
+ #puts "Migrating to #{migration.name} (#{migration.version})"
126
+
127
+ # On our way up, we skip migrating the ones we've already migrated
128
+ next if up? && migrated.include?(migration.version.to_i)
129
+
130
+ # On our way down, we skip reverting the ones we've never migrated
131
+ if down? && !migrated.include?(migration.version.to_i)
132
+ migration.announce 'never migrated, skipping'; migration.write
133
+ next
134
+ end
135
+
136
+ migration.migrate(@direction)
137
+ record_version_state_after_migrating(migration)
138
+ end
139
+ end
140
+
141
+ def migrations
142
+ @migrations ||= begin
143
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
144
+
145
+ migrations = files.inject([]) do |klasses, file|
146
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
147
+
148
+ raise IllegalMigrationNameError.new(file) unless version
149
+ version = version.to_i
150
+
151
+ if klasses.detect { |m| m.version == version }
152
+ raise DuplicateMigrationVersionError.new(version)
153
+ end
154
+
155
+ if klasses.detect { |m| m.name == name.camelize }
156
+ raise DuplicateMigrationNameError.new(name.camelize)
157
+ end
158
+
159
+ migration = MigrationProxy.new
160
+ migration.name = name.camelize
161
+ migration.version = version
162
+ migration.filename = file
163
+ klasses << migration
164
+ end
165
+
166
+ migrations = migrations.sort_by { |m| m.version }
167
+ down? ? migrations.reverse : migrations
168
+ end
169
+ end
170
+
171
+ def pending_migrations
172
+ already_migrated = migrated
173
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
174
+ end
175
+
176
+ def migrated
177
+ @migrated_versions ||= self.class.get_all_versions
178
+ end
179
+
180
+ private
181
+
182
+ def column_family_tasks
183
+ Tasks::ColumnFamily.new(connection.keyspace)
184
+ end
185
+
186
+ def connection
187
+ DatastaxRails::Base.connection
188
+ end
189
+
190
+ def record_version_state_after_migrating(migration)
191
+ sm_cf = self.class.schema_migrations_column_family
192
+
193
+ @migrated_versions ||= []
194
+ if down?
195
+ @migrated_versions.delete(migration.version)
196
+ connection.remove sm_cf, 'all', migration.version
197
+ else
198
+ @migrated_versions.push(migration.version).sort!
199
+ connection.insert sm_cf, 'all', { migration.version => migration.name }
200
+ end
201
+ end
202
+
203
+ def up?
204
+ @direction == :up
205
+ end
206
+
207
+ def down?
208
+ @direction == :down
209
+ end
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,37 @@
1
+ module DatastaxRails
2
+ module Schema
3
+ extend ActiveSupport::Autoload
4
+
5
+ class IrreversibleMigration < StandardError
6
+ end
7
+
8
+ class DuplicateMigrationVersionError < StandardError#:nodoc:
9
+ def initialize(version)
10
+ super("Multiple migrations have the version number #{version}")
11
+ end
12
+ end
13
+
14
+ class DuplicateMigrationNameError < StandardError#:nodoc:
15
+ def initialize(name)
16
+ super("Multiple migrations have the name #{name}")
17
+ end
18
+ end
19
+
20
+ class UnknownMigrationVersionError < StandardError #:nodoc:
21
+ def initialize(version)
22
+ super("No migration with version number #{version}")
23
+ end
24
+ end
25
+
26
+ class IllegalMigrationNameError < StandardError#:nodoc:
27
+ def initialize(name)
28
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
29
+ end
30
+ end
31
+
32
+ autoload :Migrator
33
+ autoload :Migration
34
+ autoload :MigrationProxy
35
+
36
+ end
37
+ end
@@ -0,0 +1,394 @@
1
+ module DatastaxRails
2
+ module Scoping
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def scoped(options = nil)
7
+ if options
8
+ scoped.apply_finder_options(options)
9
+ else
10
+ if current_scope
11
+ current_scope.clone
12
+ else
13
+ relation.clone.tap do |scope|
14
+ scope.default_scoped = true
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ # Adds a class method for retrieving and querying objects. A \scope represents a narrowing of a SOLR query,
21
+ # such as <tt>where(:color => :red).order(:size)</tt>.
22
+ #
23
+ # class Shirt < DatastaxRails::Base
24
+ # scope :red, where(:color => 'red')
25
+ # scope :dry_clean_only, where(:dry_clean => true)
26
+ # end
27
+ #
28
+ # The above calls to <tt>scope</tt> define class methods Shirt.red and Shirt.dry_clean_only. Shirt.red,
29
+ # in effect, represents the query <tt>Shirt.where(:color => 'red')</tt>.
30
+ #
31
+ # Note that this is simply 'syntactic sugar' for defining an actual class method:
32
+ #
33
+ # class Shirt < ActiveRecord::Base
34
+ # def self.red
35
+ # where(:color => 'red')
36
+ # end
37
+ # end
38
+ #
39
+ # Unlike <tt>Shirt.find(...)</tt>, however, the object returned by Shirt.red is not an Array; it
40
+ # resembles the association object constructed by a <tt>has_many</tt> declaration. For instance,
41
+ # you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>, <tt>Shirt.red.where(:size => 'small')</tt>.
42
+ # Also, just as with the association objects, named \scopes act like an Array, implementing Enumerable;
43
+ # <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt>
44
+ # all behave as if Shirt.red really was an Array.
45
+ #
46
+ # These named \scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce
47
+ # all shirts that are both red and dry clean only.
48
+ # Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
49
+ # returns the number of garments for which these criteria obtain. Similarly with
50
+ # <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
51
+ #
52
+ # All \scopes are available as class methods on the DatastaxRails::Base descendant upon which
53
+ # the \scopes were defined. But they are also available to <tt>has_many</tt> associations. If,
54
+ #
55
+ # class Person < DatastaxRails::Base
56
+ # has_many :shirts
57
+ # end
58
+ #
59
+ # then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
60
+ # only shirts.
61
+ #
62
+ # Named \scopes can also be procedural:
63
+ #
64
+ # class Shirt < DatastaxRails::Base
65
+ # scope :colored, lambda { |color| where(:color => color) }
66
+ # end
67
+ #
68
+ # In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
69
+ #
70
+ # Note that scopes defined with \scope will be evaluated when they are defined, rather than
71
+ # when they are used. For example, the following would be incorrect:
72
+ #
73
+ # class Post < DatastaxRails::Base
74
+ # scope :recent, where('published_at >= ?', Time.current - 1.week)
75
+ # end
76
+ #
77
+ # The example above would be 'frozen' to the <tt>Time.current</tt> value when the <tt>Post</tt>
78
+ # class was defined, and so the resultant SOLR query would always be the same. The correct
79
+ # way to do this would be via a lambda, which will re-evaluate the scope each time
80
+ # it is called:
81
+ #
82
+ # class Post < DatastaxRails::Base
83
+ # scope :recent, lambda { where('published_at >= ?', Time.current - 1.week) }
84
+ # end
85
+ #
86
+ # Named \scopes can also have extensions, just as with <tt>has_many</tt> declarations:
87
+ #
88
+ # class Shirt < DatastaxRails::Base
89
+ # scope :red, where(:color => 'red') do
90
+ # def dom_id
91
+ # 'red_shirts'
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ # Scopes can also be used while creating/building a record.
97
+ #
98
+ # class Article < DatastaxRails::Base
99
+ # scope :published, where(:published => true)
100
+ # end
101
+ #
102
+ # Article.published.new.published # => true
103
+ # Article.published.create.published # => true
104
+ #
105
+ # Class methods on your model are automatically available
106
+ # on scopes. Assuming the following setup:
107
+ #
108
+ # class Article < DatastaxRails::Base
109
+ # scope :published, where(:published => true)
110
+ # scope :featured, where(:featured => true)
111
+ #
112
+ # def self.latest_article
113
+ # order('published_at desc').first
114
+ # end
115
+ #
116
+ # def self.titles
117
+ # map(&:title)
118
+ # end
119
+ # end
120
+ #
121
+ # We are able to call the methods like this:
122
+ #
123
+ # Article.published.featured.latest_article
124
+ # Article.featured.titles
125
+ def scope(name, scope_options = {})
126
+ name = name.to_sym
127
+ valid_scope_name?(name)
128
+ extension = Module.new(&Proc.new) if block_given?
129
+
130
+ scope_proc = lambda do |*args|
131
+ options = scope_options.respond_to?(:call) ? scope_options.call(*args) : scope_options
132
+ options = scoped.apply_finder_options(options) if options.is_a?(Hash)
133
+
134
+ relation = scoped.merge(options)
135
+
136
+ extension ? relation.extending(extension) : relation
137
+ end
138
+
139
+ singleton_class.send(:redefine_method, name, &scope_proc)
140
+ end
141
+
142
+ # Returns a scope for this class without taking into account the default_scope.
143
+ #
144
+ # class Post < DatastaxRails::Base
145
+ # def self.default_scope
146
+ # where :published => true
147
+ # end
148
+ # end
149
+ #
150
+ # Post.all # Finds posts where +published+ is +true+
151
+ # Post.unscoped.all # Finds all posts regardless of +published+'s truthiness
152
+ #
153
+ # This method also accepts a block meaning that all queries inside the block will
154
+ # not use the default_scope:
155
+ #
156
+ # Post.unscoped {
157
+ # Post.limit(10) # Finds the first 10 posts
158
+ # }
159
+ #
160
+ # It is recommended to use block form of unscoped because chaining unscoped with <tt>scope</tt>
161
+ # does not work. Assuming that <tt>published</tt> is a <tt>scope</tt> following two statements are same.
162
+ #
163
+ # Post.unscoped.published
164
+ # Post.published
165
+ def unscoped #:nodoc:
166
+ block_given? ? relation.scoping { yield } : relation
167
+ end
168
+
169
+ def before_remove_const #:nodoc:
170
+ self.current_scope = nil
171
+ end
172
+
173
+ # with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
174
+ # <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
175
+ # <tt>:create</tt> parameters are an attributes hash.
176
+ #
177
+ # class Article < DatastaxRails::Base
178
+ # def self.create_with_scope
179
+ # with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
180
+ # find(1) # => WHERE blog_id = 1 AND id = 1
181
+ # a = create(1)
182
+ # a.blog_id # => 1
183
+ # end
184
+ # end
185
+ # end
186
+ #
187
+ # In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
188
+ # <tt>where</tt> which is merged.
189
+ #
190
+ # You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
191
+ #
192
+ # class Article < DatastaxRails::Base
193
+ # def self.find_with_exclusive_scope
194
+ # with_scope(:find => where(:blog_id => 1).limit(1)) do
195
+ # with_exclusive_scope(:find => limit(10)) do
196
+ # all # => SELECT * from articles LIMIT 10
197
+ # end
198
+ # end
199
+ # end
200
+ # end
201
+ #
202
+ # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
203
+ def with_scope(scope = {}, action = :merge, &block)
204
+ # If another DatastaxRails class has been passed in, get its current scope
205
+ scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
206
+
207
+ previous_scope = self.current_scope
208
+
209
+ if scope.is_a?(Hash)
210
+ # Dup first and second level of hash (method and params).
211
+ scope = scope.dup
212
+ scope.each do |method, params|
213
+ scope[method] = params.dup unless params == true
214
+ end
215
+
216
+ scope.assert_valid_keys([ :find, :create ])
217
+ relation = construct_finder_relation(scope[:find] || {})
218
+ relation.default_scoped = true unless action == :overwrite
219
+
220
+ if previous_scope && previous_scope.create_with_value && scope[:create]
221
+ scope_for_create = if action == :merge
222
+ previous_scope.create_with_value.merge(scope[:create])
223
+ else
224
+ scope[:create]
225
+ end
226
+
227
+ relation = relation.create_with(scope_for_create)
228
+ else
229
+ scope_for_create = scope[:create]
230
+ scope_for_create ||= previous_scope.create_with_value if previous_scope
231
+ relation = relation.create_with(scope_for_create) if scope_for_create
232
+ end
233
+
234
+ scope = relation
235
+ end
236
+
237
+ scope = previous_scope.merge(scope) if previous_scope && action == :merge
238
+
239
+ self.current_scope = scope
240
+ begin
241
+ yield
242
+ ensure
243
+ self.current_scope = previous_scope
244
+ end
245
+ end
246
+
247
+ # Works like with_scope, but discards any nested properties.
248
+ def with_exclusive_scope(method_scoping = {}, &block)
249
+ if method_scoping.values.any? { |e| e.is_a?(DatastaxRails::Relation) }
250
+ raise ArgumentError, <<-MSG
251
+ New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
252
+
253
+ User.unscoped.where(:active => true)
254
+
255
+ Or call unscoped with a block:
256
+
257
+ User.unscoped do
258
+ User.where(:active => true).all
259
+ end
260
+
261
+ MSG
262
+ end
263
+ with_scope(method_scoping, :overwrite, &block)
264
+ end
265
+
266
+ # Use this macro in your model to set a default scope for all operations on
267
+ # the model.
268
+ #
269
+ # class Article < DatastaxRails::Base
270
+ # default_scope where(:published => true)
271
+ # end
272
+ #
273
+ # Article.all # => all articles where published = true
274
+ #
275
+ # The <tt>default_scope</tt> is also applied while creating/building a record. It is not
276
+ # applied while updating a record.
277
+ #
278
+ # Article.new.published # => true
279
+ # Article.create.published # => true
280
+ #
281
+ # You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
282
+ #
283
+ # class Article < DatastaxRails::Base
284
+ # default_scope { where(:published_at => Time.now - 1.week) }
285
+ # end
286
+ #
287
+ # (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
288
+ # macro, and it will be called when building the default scope.)
289
+ #
290
+ # If you use multiple <tt>default_scope</tt> declarations in your model then they will
291
+ # be merged together:
292
+ #
293
+ # class Article < DatastaxRails::Base
294
+ # default_scope where(:published => true)
295
+ # default_scope where(:rating => 'G')
296
+ # end
297
+ #
298
+ # Article.all # => all articles where published = true AND rating = 'G'
299
+ #
300
+ # This is also the case with inheritance and module includes where the parent or module
301
+ # defines a <tt>default_scope</tt> and the child or including class defines a second one.
302
+ #
303
+ # If you need to do more complex things with a default scope, you can alternatively
304
+ # define it as a class method:
305
+ #
306
+ # class Article < DatastaxRails::Base
307
+ # def self.default_scope
308
+ # # Should return a scope, you can call 'super' here etc.
309
+ # end
310
+ # end
311
+ def default_scope(scope = {})
312
+ scope = Proc.new if block_given?
313
+ self.default_scopes = default_scopes + [scope]
314
+ end
315
+
316
+ def build_default_scope #:nodoc:
317
+ if method(:default_scope).owner != Base.singleton_class
318
+ evaluate_default_scope { default_scope }
319
+ elsif default_scopes.any?
320
+ evaluate_default_scope do
321
+ default_scopes.inject(relation) do |default_scope, scope|
322
+ if scope.is_a?(Hash)
323
+ default_scope.apply_finder_options(scope)
324
+ elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
325
+ default_scope.merge(scope.call)
326
+ else
327
+ default_scope.merge(scope)
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
333
+
334
+ def ignore_default_scope? #:nodoc:
335
+ Thread.current["#{self}_ignore_default_scope"]
336
+ end
337
+
338
+ def ignore_default_scope=(ignore) #:nodoc:
339
+ Thread.current["#{self}_ignore_default_scope"] = ignore
340
+ end
341
+
342
+ # The ignore_default_scope flag is used to prevent an infinite recursion situation where
343
+ # a default scope references a scope which has a default scope which references a scope...
344
+ def evaluate_default_scope
345
+ return if ignore_default_scope?
346
+
347
+ begin
348
+ self.ignore_default_scope = true
349
+ yield
350
+ ensure
351
+ self.ignore_default_scope = false
352
+ end
353
+ end
354
+
355
+ # Collects attributes from scopes that should be applied when creating
356
+ # an SO instance for the particular class this is called on.
357
+ def scope_attributes # :nodoc:
358
+ if current_scope
359
+ current_scope.scope_for_create
360
+ else
361
+ relation.clone.tap do |scope|
362
+ scope.default_scoped = true
363
+ end
364
+ end
365
+ end
366
+
367
+ # Are there default attributes associated with this scope?
368
+ def scope_attributes? # :nodoc:
369
+ current_scope || default_scopes.any?
370
+ end
371
+
372
+ protected
373
+
374
+ def current_scope #:nodoc:
375
+ Thread.current["#{self}_current_scope"]
376
+ end
377
+
378
+ def current_scope=(scope) #:nodoc:
379
+ Thread.current["#{self}_current_scope"] = scope
380
+ end
381
+
382
+ def apply_default_scope
383
+
384
+ end
385
+
386
+ def valid_scope_name?(name)
387
+ if respond_to?(name, true)
388
+ logger.warn "Creating scope :#{name}. " \
389
+ "Overwriting existing method #{self.name}.#{name}."
390
+ end
391
+ end
392
+ end
393
+ end
394
+ end
@@ -0,0 +1,6 @@
1
+ module DatastaxRails
2
+ module Serialization
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Serializers::JSON
5
+ end
6
+ end