bigrecord 0.0.5

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 (104) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +44 -0
  3. data/Rakefile +17 -0
  4. data/VERSION +1 -0
  5. data/doc/bigrecord_specs.rdoc +36 -0
  6. data/doc/getting_started.rdoc +157 -0
  7. data/examples/bigrecord.yml +25 -0
  8. data/generators/bigrecord/bigrecord_generator.rb +17 -0
  9. data/generators/bigrecord/templates/bigrecord.rake +47 -0
  10. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
  11. data/generators/bigrecord_migration/templates/migration.rb +9 -0
  12. data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
  13. data/generators/bigrecord_model/templates/migration.rb +13 -0
  14. data/generators/bigrecord_model/templates/model.rb +7 -0
  15. data/generators/bigrecord_model/templates/model_spec.rb +12 -0
  16. data/init.rb +9 -0
  17. data/install.rb +22 -0
  18. data/lib/big_record/abstract_base.rb +1088 -0
  19. data/lib/big_record/action_view_extensions.rb +266 -0
  20. data/lib/big_record/ar_associations/association_collection.rb +194 -0
  21. data/lib/big_record/ar_associations/association_proxy.rb +158 -0
  22. data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
  23. data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
  24. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
  25. data/lib/big_record/ar_associations/has_many_association.rb +191 -0
  26. data/lib/big_record/ar_associations/has_one_association.rb +80 -0
  27. data/lib/big_record/ar_associations.rb +1608 -0
  28. data/lib/big_record/ar_reflection.rb +223 -0
  29. data/lib/big_record/attribute_methods.rb +75 -0
  30. data/lib/big_record/base.rb +618 -0
  31. data/lib/big_record/br_associations/association_collection.rb +194 -0
  32. data/lib/big_record/br_associations/association_proxy.rb +153 -0
  33. data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
  34. data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
  35. data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
  36. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
  37. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
  38. data/lib/big_record/br_associations/has_one_association.rb +80 -0
  39. data/lib/big_record/br_associations.rb +978 -0
  40. data/lib/big_record/br_reflection.rb +151 -0
  41. data/lib/big_record/callbacks.rb +367 -0
  42. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
  43. data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
  44. data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
  45. data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
  46. data/lib/big_record/connection_adapters/column.rb +491 -0
  47. data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
  48. data/lib/big_record/connection_adapters/view.rb +27 -0
  49. data/lib/big_record/connection_adapters.rb +10 -0
  50. data/lib/big_record/deletion.rb +73 -0
  51. data/lib/big_record/dynamic_schema.rb +92 -0
  52. data/lib/big_record/embedded.rb +71 -0
  53. data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
  54. data/lib/big_record/family_span_columns.rb +89 -0
  55. data/lib/big_record/fixtures.rb +1025 -0
  56. data/lib/big_record/migration.rb +380 -0
  57. data/lib/big_record/routing_ext.rb +65 -0
  58. data/lib/big_record/timestamp.rb +51 -0
  59. data/lib/big_record/validations.rb +830 -0
  60. data/lib/big_record.rb +125 -0
  61. data/lib/bigrecord.rb +1 -0
  62. data/rails/init.rb +9 -0
  63. data/spec/connections/bigrecord.yml +13 -0
  64. data/spec/connections/cassandra/connection.rb +2 -0
  65. data/spec/connections/hbase/connection.rb +2 -0
  66. data/spec/debug.log +281 -0
  67. data/spec/integration/br_associations_spec.rb +80 -0
  68. data/spec/lib/animal.rb +12 -0
  69. data/spec/lib/book.rb +10 -0
  70. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
  71. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
  72. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
  73. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
  74. data/spec/lib/company.rb +14 -0
  75. data/spec/lib/embedded/web_link.rb +12 -0
  76. data/spec/lib/employee.rb +33 -0
  77. data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
  78. data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
  79. data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
  80. data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
  81. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
  82. data/spec/lib/novel.rb +5 -0
  83. data/spec/lib/zoo.rb +17 -0
  84. data/spec/spec.opts +4 -0
  85. data/spec/spec_helper.rb +55 -0
  86. data/spec/unit/abstract_base_spec.rb +287 -0
  87. data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
  88. data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
  89. data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
  90. data/spec/unit/ar_associations_spec.rb +8 -0
  91. data/spec/unit/base_spec.rb +6 -0
  92. data/spec/unit/br_associations_spec.rb +58 -0
  93. data/spec/unit/embedded_spec.rb +43 -0
  94. data/spec/unit/find_spec.rb +34 -0
  95. data/spec/unit/hash_helper_spec.rb +44 -0
  96. data/spec/unit/migration_spec.rb +144 -0
  97. data/spec/unit/model_spec.rb +315 -0
  98. data/spec/unit/validations_spec.rb +182 -0
  99. data/tasks/bigrecord_tasks.rake +47 -0
  100. data/tasks/data_store.rb +46 -0
  101. data/tasks/gem.rb +22 -0
  102. data/tasks/rdoc.rb +8 -0
  103. data/tasks/spec.rb +34 -0
  104. metadata +189 -0
@@ -0,0 +1,380 @@
1
+ # require 'active_support/core_ext/object/metaclass'
2
+
3
+ # Instead of requiring the activesupport class (metaclass)
4
+ class Object
5
+ # Get object's meta (ghost, eigenclass, singleton) class
6
+ def metaclass
7
+ class << self
8
+ self
9
+ end
10
+ end
11
+
12
+ # If class_eval is called on an object, add those methods to its metaclass
13
+ def class_eval(*args, &block)
14
+ metaclass.class_eval(*args, &block)
15
+ end
16
+ end
17
+
18
+ module BigRecord
19
+ class IrreversibleMigration < BigRecordError#:nodoc:
20
+ end
21
+
22
+ class DuplicateMigrationVersionError < BigRecordError#:nodoc:
23
+ def initialize(version)
24
+ super("Multiple migrations have the version number #{version}")
25
+ end
26
+ end
27
+
28
+ class DuplicateMigrationNameError < BigRecordError#:nodoc:
29
+ def initialize(name)
30
+ super("Multiple migrations have the name #{name}")
31
+ end
32
+ end
33
+
34
+ class UnknownMigrationVersionError < BigRecordError #:nodoc:
35
+ def initialize(version)
36
+ super("No migration with version number #{version}")
37
+ end
38
+ end
39
+
40
+ class IllegalMigrationNameError < BigRecordError#:nodoc:
41
+ def initialize(name)
42
+ super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
43
+ end
44
+ end
45
+
46
+ # Although column-oriented databases are generally schema-less, certain ones (like Hbase)
47
+ # require the creation of tables and column families ahead of time. The individual columns,
48
+ # however, are defined in the model itself and can be modified dynamically without the need for migrations.
49
+ #
50
+ # Unless you're familiar with column families, the majority of use cases work perfectly fine within one
51
+ # column family. When you generate a bigrecord_model, it will default to creating the :attribute column family.
52
+ #
53
+ # The following is a standard migration file that creates a table called "Books" with the default
54
+ # column family :attribute that has the following option of 100 versions and uses the 'lzo' compression scheme.
55
+ # Leave any options blank for the default value.
56
+ #
57
+ # class CreateBooks < BigRecord::Migration
58
+ # def self.up
59
+ # create_table :books, :force => true do |t|
60
+ # t.family :attribute, :versions => 100, :compression => 'lzo'
61
+ # end
62
+ # end
63
+ #
64
+ # def self.down
65
+ # drop_table :books
66
+ # end
67
+ # end
68
+ #
69
+ class Migration
70
+ @@verbose = true
71
+ cattr_accessor :verbose
72
+
73
+ class << self
74
+ def up_with_benchmarks #:nodoc:
75
+ migrate(:up)
76
+ end
77
+
78
+ def down_with_benchmarks #:nodoc:
79
+ migrate(:down)
80
+ end
81
+
82
+ # Execute this migration in the named direction
83
+ def migrate(direction)
84
+ return unless respond_to?(direction)
85
+
86
+ case direction
87
+ when :up then announce "migrating"
88
+ when :down then announce "reverting"
89
+ end
90
+
91
+ result = nil
92
+ time = Benchmark.measure { result = send("#{direction}_without_benchmarks") }
93
+
94
+ case direction
95
+ when :up then announce "migrated (%.4fs)" % time.real; write
96
+ when :down then announce "reverted (%.4fs)" % time.real; write
97
+ end
98
+
99
+ result
100
+ end
101
+
102
+ # Because the method added may do an alias_method, it can be invoked
103
+ # recursively. We use @ignore_new_methods as a guard to indicate whether
104
+ # it is safe for the call to proceed.
105
+ def singleton_method_added(sym) #:nodoc:
106
+ return if defined?(@ignore_new_methods) && @ignore_new_methods
107
+
108
+ begin
109
+ @ignore_new_methods = true
110
+
111
+ case sym
112
+ when :up, :down
113
+ metaclass.send(:alias_method_chain, sym, "benchmarks")
114
+ end
115
+ ensure
116
+ @ignore_new_methods = false
117
+ end
118
+ end
119
+
120
+ def write(text="")
121
+ puts(text) if verbose
122
+ end
123
+
124
+ def announce(message)
125
+ text = "#{@version} #{name}: #{message}"
126
+ length = [0, 75 - text.length].max
127
+ write "== %s %s" % [text, "=" * length]
128
+ end
129
+
130
+ def say(message, subitem=false)
131
+ write "#{subitem ? " ->" : "--"} #{message}"
132
+ end
133
+
134
+ def say_with_time(message)
135
+ say(message)
136
+ result = nil
137
+ time = Benchmark.measure { result = yield }
138
+ say "%.4fs" % time.real, :subitem
139
+ say("#{result} rows", :subitem) if result.is_a?(Integer)
140
+ result
141
+ end
142
+
143
+ def suppress_messages
144
+ save, self.verbose = verbose, false
145
+ yield
146
+ ensure
147
+ self.verbose = save
148
+ end
149
+
150
+ def connection
151
+ BigRecord::Base.connection
152
+ end
153
+
154
+ def method_missing(method, *arguments, &block)
155
+ arg_list = arguments.map(&:inspect) * ', '
156
+
157
+ say_with_time "#{method}(#{arg_list})" do
158
+ unless arguments.empty? || method == :execute
159
+ arguments[0] = Migrator.proper_table_name(arguments.first)
160
+ end
161
+ connection.send(method, *arguments, &block)
162
+ end
163
+ end
164
+ end
165
+ end
166
+
167
+ # MigrationProxy is used to defer loading of the actual migration classes
168
+ # until they are needed
169
+ class MigrationProxy
170
+
171
+ attr_accessor :name, :version, :filename
172
+
173
+ delegate :migrate, :announce, :write, :to=>:migration
174
+
175
+ private
176
+
177
+ def migration
178
+ @migration ||= load_migration
179
+ end
180
+
181
+ def load_migration
182
+ load(filename)
183
+ name.constantize
184
+ end
185
+
186
+ end
187
+
188
+ class Migrator#:nodoc:
189
+ class << self
190
+ def migrate(migrations_path, target_version = nil)
191
+ case
192
+ when target_version.nil? then up(migrations_path, target_version)
193
+ when current_version > target_version then down(migrations_path, target_version)
194
+ else up(migrations_path, target_version)
195
+ end
196
+ end
197
+
198
+ def rollback(migrations_path, steps=1)
199
+ migrator = self.new(:down, migrations_path)
200
+ start_index = migrator.migrations.index(migrator.current_migration)
201
+
202
+ return unless start_index
203
+
204
+ finish = migrator.migrations[start_index + steps]
205
+ down(migrations_path, finish ? finish.version : 0)
206
+ end
207
+
208
+ def up(migrations_path, target_version = nil)
209
+ self.new(:up, migrations_path, target_version).migrate
210
+ end
211
+
212
+ def down(migrations_path, target_version = nil)
213
+ self.new(:down, migrations_path, target_version).migrate
214
+ end
215
+
216
+ def run(direction, migrations_path, target_version)
217
+ self.new(direction, migrations_path, target_version).run
218
+ end
219
+
220
+ def schema_migrations_table_name
221
+ Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
222
+ end
223
+
224
+ def get_all_versions
225
+ Base.connection.get_all_schema_versions.sort
226
+ end
227
+
228
+ def current_version
229
+ sm_table = schema_migrations_table_name
230
+ if Base.connection.table_exists?(sm_table)
231
+ get_all_versions.max || 0
232
+ else
233
+ 0
234
+ end
235
+ end
236
+
237
+ def proper_table_name(name)
238
+ # Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
239
+ name.table_name rescue "#{BigRecord::Base.table_name_prefix}#{name}#{BigRecord::Base.table_name_suffix}"
240
+ end
241
+ end
242
+
243
+ def initialize(direction, migrations_path, target_version = nil)
244
+ raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
245
+ Base.connection.initialize_schema_migrations_table
246
+ @direction, @migrations_path, @target_version = direction, migrations_path, target_version
247
+ end
248
+
249
+ def current_version
250
+ migrated.last || 0
251
+ end
252
+
253
+ def current_migration
254
+ migrations.detect { |m| m.version == current_version }
255
+ end
256
+
257
+ def run
258
+ target = migrations.detect { |m| m.version == @target_version }
259
+ raise UnknownMigrationVersionError.new(@target_version) if target.nil?
260
+ unless (up? && migrated.include?(target.version.to_i)) || (down? && !migrated.include?(target.version.to_i))
261
+ target.migrate(@direction)
262
+ record_version_state_after_migrating(target.version)
263
+ end
264
+ end
265
+
266
+ def migrate
267
+ current = migrations.detect { |m| m.version == current_version }
268
+ target = migrations.detect { |m| m.version == @target_version }
269
+
270
+ if target.nil? && !@target_version.nil? && @target_version > 0
271
+ raise UnknownMigrationVersionError.new(@target_version)
272
+ end
273
+
274
+ start = up? ? 0 : (migrations.index(current) || 0)
275
+ finish = migrations.index(target) || migrations.size - 1
276
+ runnable = migrations[start..finish]
277
+
278
+ # skip the last migration if we're headed down, but not ALL the way down
279
+ runnable.pop if down? && !target.nil?
280
+
281
+ runnable.each do |migration|
282
+ Base.logger.info "Migrating to #{migration.name} (#{migration.version})"
283
+
284
+ # On our way up, we skip migrating the ones we've already migrated
285
+ next if up? && migrated.include?(migration.version.to_i)
286
+
287
+ # On our way down, we skip reverting the ones we've never migrated
288
+ if down? && !migrated.include?(migration.version.to_i)
289
+ migration.announce 'never migrated, skipping'; migration.write
290
+ next
291
+ end
292
+
293
+ begin
294
+ ddl_transaction do
295
+ migration.migrate(@direction)
296
+ record_version_state_after_migrating(migration.version)
297
+ end
298
+ rescue => e
299
+ canceled_msg = Base.connection.supports_ddl_transactions? ? "this and " : ""
300
+ raise StandardError, "An error has occurred, #{canceled_msg}all later migrations canceled:\n\n#{e}", e.backtrace
301
+ end
302
+ end
303
+ end
304
+
305
+ def migrations
306
+ @migrations ||= begin
307
+ files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
308
+
309
+ migrations = files.inject([]) do |klasses, file|
310
+ version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
311
+
312
+ raise IllegalMigrationNameError.new(file) unless version
313
+ version = version.to_i
314
+
315
+ if klasses.detect { |m| m.version == version }
316
+ raise DuplicateMigrationVersionError.new(version)
317
+ end
318
+
319
+ if klasses.detect { |m| m.name == name.camelize }
320
+ raise DuplicateMigrationNameError.new(name.camelize)
321
+ end
322
+
323
+ migration = MigrationProxy.new
324
+ migration.name = name.camelize
325
+ migration.version = version
326
+ migration.filename = file
327
+ klasses << migration
328
+ end
329
+
330
+ migrations = migrations.sort_by(&:version)
331
+ down? ? migrations.reverse : migrations
332
+ end
333
+ end
334
+
335
+ def pending_migrations
336
+ already_migrated = migrated
337
+ migrations.reject { |m| already_migrated.include?(m.version.to_i) }
338
+ end
339
+
340
+ def migrated
341
+ @migrated_versions ||= self.class.get_all_versions
342
+ end
343
+
344
+ private
345
+ def record_version_state_after_migrating(version)
346
+ sm_table = self.class.schema_migrations_table_name
347
+
348
+ @migrated_versions ||= []
349
+ if down?
350
+ @migrated_versions.delete(version.to_i)
351
+
352
+ # TODO: Consider putting this in the adapter instead
353
+ Base.connection.delete(sm_table, version.to_s)
354
+ else
355
+ @migrated_versions.push(version.to_i).sort!
356
+
357
+ timestamp = Time.parse(version.to_s).to_bigrecord_timestamp
358
+ # TODO: Consider putting this in the adapter instead
359
+ Base.connection.update(sm_table, version.to_s, {'attribute:version' => version}, timestamp)
360
+ end
361
+ end
362
+
363
+ def up?
364
+ @direction == :up
365
+ end
366
+
367
+ def down?
368
+ @direction == :down
369
+ end
370
+
371
+ # Wrap the migration in a transaction only if supported by the adapter.
372
+ def ddl_transaction(&block)
373
+ if Base.connection.supports_ddl_transactions?
374
+ Base.transaction { block.call }
375
+ else
376
+ block.call
377
+ end
378
+ end
379
+ end
380
+ end
@@ -0,0 +1,65 @@
1
+ # The following overrides are required because we use strings as ids and it's
2
+ # possible that these strings are not clean. The default implementation
3
+ # escapes them with URI.escape() but its' not good. e.g. '&' becomes &amp; instead of %26
4
+ module ActionController
5
+ module Routing
6
+ class PathSegment
7
+ def interpolation_chunk(value_code = "#{local_name}")
8
+ "\#{CGI.escape(#{value_code}.to_s)}"
9
+ end
10
+
11
+ class Result
12
+ def self.new_escaped(strings)
13
+ new strings.collect {|str| CGI.unescape(str)}
14
+ end
15
+ end
16
+ end
17
+
18
+ class DynamicSegment
19
+ def interpolation_chunk(value_code = "#{local_name}")
20
+ "\#{CGI.escape(#{value_code}.to_s)}"
21
+ end
22
+
23
+ def match_extraction(next_capture)
24
+ # All non code-related keys (such as :id, :slug) are URI-unescaped as
25
+ # path parameters.
26
+ default_value = default ? default.inspect : nil
27
+ %[
28
+ value = if (m = match[#{next_capture}])
29
+ CGI.unescape(m)
30
+ else
31
+ #{default_value}
32
+ end
33
+ params[:#{key}] = value if value
34
+ ]
35
+ end
36
+ end
37
+ end
38
+
39
+ # TODO: Remove this monkey patch once we migrate to rails 2.3. This patch
40
+ # add support for the :as option in map.resources.
41
+ module Resources
42
+ class Resource
43
+
44
+ attr_reader :path_segment
45
+
46
+ def initialize(entities, options)
47
+ @plural ||= entities
48
+ @singular ||= options[:singular] || plural.to_s.singularize
49
+ @path_segment = options.delete(:as) || @plural
50
+
51
+ @options = options
52
+
53
+ arrange_actions
54
+ add_default_actions
55
+ set_prefixes
56
+ end
57
+
58
+ def path
59
+ @path ||= "#{path_prefix}/#{path_segment}"
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ end
@@ -0,0 +1,51 @@
1
+ module BigRecord
2
+ # Active Record automatically timestamps create and update if the table has fields
3
+ # created_at/created_on or updated_at/updated_on.
4
+ #
5
+ # Timestamping can be turned off by setting
6
+ # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
+ #
8
+ # Keep in mind that, via inheritance, you can turn off timestamps on a per
9
+ # model basis by setting <tt>record_timestamps</tt> to false in the desired
10
+ # models.
11
+ #
12
+ # class Feed < ActiveRecord::Base
13
+ # self.record_timestamps = false
14
+ # # ...
15
+ # end
16
+ #
17
+ # Timestamps are in the local timezone by default but can use UTC by setting
18
+ # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
19
+ module Timestamp
20
+ def self.included(base) #:nodoc:
21
+ super
22
+
23
+ base.alias_method_chain :create, :timestamps
24
+ base.alias_method_chain :update, :timestamps
25
+
26
+ base.cattr_accessor :record_timestamps, :instance_writer => false
27
+ base.record_timestamps = true
28
+ end
29
+
30
+ def create_with_timestamps #:nodoc:
31
+ if record_timestamps
32
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
33
+ self.send(:created_at=, t) if respond_to?(:created_at) && created_at.nil?
34
+ self.send(:created_on=, t) if respond_to?(:created_on) && created_on.nil?
35
+
36
+ self.send(:updated_at=, t) if respond_to?(:updated_at)
37
+ self.send(:updated_on=, t) if respond_to?(:updated_on)
38
+ end
39
+ create_without_timestamps
40
+ end
41
+
42
+ def update_with_timestamps #:nodoc:
43
+ if record_timestamps
44
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
45
+ self.send(:updated_at=, t) if respond_to?(:updated_at)
46
+ self.send(:updated_on=, t) if respond_to?(:updated_on)
47
+ end
48
+ update_without_timestamps
49
+ end
50
+ end
51
+ end