bigrecord 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
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,432 @@
1
+ require 'rubygems'
2
+ require 'set'
3
+ require 'drb'
4
+
5
+ unless defined?(BigRecordDriver)
6
+ begin
7
+ # Bigrecord's source is included with Bigrecord-Driver, that's we check for this first
8
+ require File.join(File.dirname(__FILE__), "..", "..", "..", "..", "bigrecord-driver", "lib", "big_record_driver")
9
+ rescue LoadError
10
+ begin
11
+ gem 'bigrecord-driver'
12
+ require 'bigrecord_driver'
13
+ rescue Gem::LoadError
14
+ puts "bigrecord-driver not available. Install it with: sudo gem install bigrecord-driver -s http://gemcutter.org"
15
+ end
16
+ end
17
+ end
18
+
19
+ module BigRecord
20
+ class Base
21
+ # Establishes a connection to the database that's used by all Active Record objects.
22
+ def self.hbase_connection(config) # :nodoc:
23
+ config = config.symbolize_keys
24
+
25
+ zookeeper_host = config[:zookeeper_host]
26
+ zookeeper_client_port = config[:zookeeper_client_port]
27
+ drb_host = config[:drb_host]
28
+ drb_port = config[:drb_port]
29
+
30
+ hbase = BigRecordDriver::Client.new(config)
31
+
32
+ ConnectionAdapters::HbaseAdapter.new(hbase, logger, [zookeeper_host, zookeeper_client_port], config)
33
+ end
34
+ end
35
+
36
+ module ConnectionAdapters
37
+ class HbaseAdapter < AbstractAdapter
38
+ @@emulate_booleans = true
39
+ cattr_accessor :emulate_booleans
40
+
41
+ LOST_CONNECTION_ERROR_MESSAGES = [
42
+ "Server shutdown in progress",
43
+ "Broken pipe",
44
+ "Lost connection to HBase server during query",
45
+ "HBase server has gone away"
46
+ ]
47
+
48
+ # data types
49
+ TYPE_NULL = 0x00;
50
+ TYPE_STRING = 0x01; # utf-8 strings
51
+ # TYPE_INTEGER = 0x02; # delegate to YAML
52
+ # TYPE_FLOAT = 0x03; # fixed 1 byte
53
+ TYPE_BOOLEAN = 0x04; # delegate to YAML
54
+ # TYPE_MAP = 0x05; # delegate to YAML
55
+ # TYPE_DATETIME = 0x06; # delegate to YAML
56
+ TYPE_BINARY = 0x07; # byte[] => no conversion
57
+
58
+ # string charset
59
+ CHARSET = "utf-8"
60
+
61
+ # utility constants
62
+ NULL = "\000"
63
+ # TRUE = "\001"
64
+ # FALSE = "\000"
65
+
66
+ def initialize(connection, logger, connection_options, config)
67
+ super(connection, logger)
68
+ @connection_options, @config = connection_options, config
69
+
70
+ connect
71
+ end
72
+
73
+ def configuration
74
+ @config.clone
75
+ end
76
+
77
+ def adapter_name #:nodoc:
78
+ 'HBase'
79
+ end
80
+
81
+ def supports_migrations? #:nodoc:
82
+ true
83
+ end
84
+
85
+ # CONNECTION MANAGEMENT ====================================
86
+
87
+ def active?
88
+ @connection.ping
89
+ rescue BigRecordError
90
+ false
91
+ end
92
+
93
+ def reconnect!
94
+ disconnect!
95
+ connect
96
+ end
97
+
98
+ def disconnect!
99
+ @connection.close rescue nil
100
+ end
101
+
102
+
103
+ # DATABASE STATEMENTS ======================================
104
+
105
+ def update_raw(table_name, row, values, timestamp)
106
+ result = nil
107
+ log "UPDATE #{table_name} SET #{values.inspect if values} WHERE ROW=#{row};" do
108
+ result = @connection.update(table_name, row, values, timestamp)
109
+ end
110
+ result
111
+ end
112
+
113
+ def update(table_name, row, values, timestamp)
114
+ serialized_collection = {}
115
+ values.each do |column, value|
116
+ serialized_collection[column] = serialize(value)
117
+ end
118
+ update_raw(table_name, row, serialized_collection, timestamp)
119
+ end
120
+
121
+ def get_raw(table_name, row, column, options={})
122
+ result = nil
123
+ log "SELECT (#{column}) FROM #{table_name} WHERE ROW=#{row};" do
124
+ result = @connection.get(table_name, row, column, options)
125
+ end
126
+ result
127
+ end
128
+
129
+ def get(table_name, row, column, options={})
130
+ serialized_result = get_raw(table_name, row, column, options)
131
+ result = nil
132
+ if serialized_result.is_a?(Array)
133
+ result = serialized_result.collect{|e| deserialize(e)}
134
+ else
135
+ result = deserialize(serialized_result)
136
+ end
137
+ result
138
+ end
139
+
140
+ def get_columns_raw(table_name, row, columns, options={})
141
+ result = {}
142
+ log "SELECT (#{columns.join(", ")}) FROM #{table_name} WHERE ROW=#{row};" do
143
+ result = @connection.get_columns(table_name, row, columns, options)
144
+ end
145
+ result
146
+ end
147
+
148
+ def get_columns(table_name, row, columns, options={})
149
+ row_cols = get_columns_raw(table_name, row, columns, options)
150
+ result = {}
151
+ return nil unless row_cols
152
+
153
+ row_cols.each do |key, col|
154
+ result[key] =
155
+ if key == 'id'
156
+ col
157
+ else
158
+ deserialize(col)
159
+ end
160
+ end
161
+ result
162
+ end
163
+
164
+ def get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row = nil)
165
+ result = nil
166
+ log "SCAN (#{columns.join(", ")}) FROM #{table_name} WHERE START_ROW=#{start_row} AND STOP_ROW=#{stop_row} LIMIT=#{limit};" do
167
+ result = @connection.get_consecutive_rows(table_name, start_row, limit, columns, stop_row)
168
+ end
169
+ result
170
+ end
171
+
172
+ def get_consecutive_rows(table_name, start_row, limit, columns, stop_row = nil)
173
+ rows = get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row)
174
+ result = rows.collect do |row_cols|
175
+ cols = {}
176
+ row_cols.each do |key, col|
177
+ begin
178
+ cols[key] =
179
+ if key == 'id'
180
+ col
181
+ else
182
+ deserialize(col)
183
+ end
184
+ rescue Exception => e
185
+ puts "Could not load column value #{key} for row=#{row_cols['id']}"
186
+ end
187
+ end
188
+ cols
189
+ end
190
+ result
191
+ end
192
+
193
+ def delete(table_name, row, timestamp = nil)
194
+ timestamp ||= Time.now.to_bigrecord_timestamp
195
+ result = nil
196
+ log "DELETE FROM #{table_name} WHERE ROW=#{row};" do
197
+ result = @connection.delete(table_name, row, timestamp)
198
+ end
199
+ result
200
+ end
201
+
202
+ def truncate_table(table_name)
203
+ result = nil
204
+ log "TRUNCATE TABLE #{table_name}" do
205
+ result = @connection.truncate_table(table_name)
206
+ end
207
+ result
208
+ end
209
+
210
+
211
+ # SCHEMA STATEMENTS ========================================
212
+
213
+ def initialize_schema_migrations_table
214
+ sm_table = BigRecord::Migrator.schema_migrations_table_name
215
+
216
+ unless table_exists?(sm_table)
217
+ create_table(sm_table) do |t|
218
+ t.family :attribute, :versions => 1
219
+ end
220
+ end
221
+ end
222
+
223
+ def get_all_schema_versions
224
+ sm_table = BigRecord::Migrator.schema_migrations_table_name
225
+
226
+ get_consecutive_rows(sm_table, nil, nil, ["attribute:version"]).map{|version| version["attribute:version"]}
227
+ end
228
+
229
+ def table_exists?(table_name)
230
+ log "TABLE EXISTS? #{table_name};" do
231
+ @connection.table_exists?(table_name)
232
+ end
233
+ end
234
+
235
+ def create_table(table_name, options = {})
236
+ table_definition = TableDefinition.new
237
+
238
+ yield table_definition if block_given?
239
+
240
+ if options[:force] && table_exists?(table_name)
241
+ drop_table(table_name)
242
+ end
243
+
244
+ result = nil
245
+ log "CREATE TABLE #{table_name} (#{table_definition.column_families_list});" do
246
+ result = @connection.create_table(table_name, table_definition.to_adapter_format)
247
+ end
248
+ result
249
+ end
250
+
251
+ def drop_table(table_name)
252
+ result = nil
253
+ log "DROP TABLE #{table_name};" do
254
+ result = @connection.drop_table(table_name)
255
+ end
256
+ result
257
+ end
258
+
259
+ def add_column_family(table_name, column_name, options = {})
260
+ column = BigRecordDriver::ColumnDescriptor.new(column_name.to_s, options)
261
+
262
+ result = nil
263
+ log "ADD COLUMN TABLE #{table_name} COLUMN #{column_name} (#{options.inspect});" do
264
+ result = @connection.add_column(table_name, column)
265
+ end
266
+ result
267
+ end
268
+
269
+ alias :add_family :add_column_family
270
+
271
+ def remove_column_family(table_name, column_name)
272
+ result = nil
273
+ log "REMOVE COLUMN TABLE #{table_name} COLUMN #{column_name};" do
274
+ result = @connection.remove_column(table_name, column_name)
275
+ end
276
+ result
277
+ end
278
+
279
+ alias :remove_family :remove_column_family
280
+
281
+ def modify_column_family(table_name, column_name, options = {})
282
+ column = BigRecordDriver::ColumnDescriptor.new(column_name.to_s, options)
283
+
284
+ result = nil
285
+ log "MODIFY COLUMN TABLE #{table_name} COLUMN #{column_name} (#{options.inspect});" do
286
+ result = @connection.modify_column(table_name, column)
287
+ end
288
+ result
289
+ end
290
+
291
+ alias :modify_family :modify_column_family
292
+
293
+ # Serialize the given value
294
+ def serialize(value)
295
+ case value
296
+ when NilClass then NULL
297
+ when String then build_serialized_value(TYPE_STRING, value)
298
+ else value.to_yaml
299
+ end
300
+ end
301
+
302
+ # Serialize an object in a given type
303
+ def build_serialized_value(type, value)
304
+ type.chr + value
305
+ end
306
+
307
+ # Deserialize the given string. This method supports both the pure YAML format and
308
+ # the type header format.
309
+ def deserialize(str)
310
+ return unless str
311
+
312
+ # stay compatible with the old serialization code
313
+ # YAML documents start with "--- " so if we find that sequence at the beginning we
314
+ # consider it as a serialized YAML value, else it's the new format with the type header
315
+ if str[0..3] == "--- "
316
+ YAML::load(str) if str
317
+ else
318
+ deserialize_with_header(str)
319
+ end
320
+ end
321
+
322
+ # Deserialize the given string assumed to be in the type header format.
323
+ def deserialize_with_header(data)
324
+ return unless data and data.size >= 2
325
+
326
+ # the type of the data is encoded in the first byte
327
+ type = data[0];
328
+
329
+ case type
330
+ when TYPE_NULL then nil
331
+ when TYPE_STRING then data[1..-1]
332
+ when TYPE_BINARY then data[1..-1]
333
+ else nil
334
+ end
335
+ end
336
+
337
+ private
338
+ def connect
339
+ @connection.configure(@config)
340
+ rescue DRb::DRbConnError
341
+ raise BigRecord::ConnectionFailed, "Failed to connect to the DRb server (jruby) " +
342
+ "at #{@config[:drb_host]}:#{@config[:drb_port]}."
343
+ end
344
+
345
+ protected
346
+ def log(str, name = nil)
347
+ if block_given?
348
+ if @logger and @logger.level <= Logger::INFO
349
+ result = nil
350
+ seconds = Benchmark.realtime { result = yield }
351
+ @runtime += seconds
352
+ log_info(str, name, seconds)
353
+ result
354
+ else
355
+ yield
356
+ end
357
+ else
358
+ log_info(str, name, 0)
359
+ nil
360
+ end
361
+ rescue Exception => e
362
+ # Log message and raise exception.
363
+ # Set last_verfication to 0, so that connection gets verified
364
+ # upon reentering the request loop
365
+ @last_verification = 0
366
+ message = "#{e.class.name}: #{e.message}: #{str}"
367
+ log_info(message, name, 0)
368
+ raise e
369
+ end
370
+
371
+ def log_info(str, name, runtime)
372
+ return unless @logger
373
+
374
+ @logger.debug(
375
+ format_log_entry(
376
+ "#{name.nil? ? "HBASE" : name} (#{sprintf("%f", runtime)})",
377
+ str.gsub(/ +/, " ")
378
+ )
379
+ )
380
+ end
381
+
382
+ def format_log_entry(message, dump = nil)
383
+ if BigRecord::Base.colorize_logging
384
+ if @@row_even
385
+ @@row_even = false
386
+ message_color, dump_color = "4;36;1", "0;1"
387
+ else
388
+ @@row_even = true
389
+ message_color, dump_color = "4;35;1", "0"
390
+ end
391
+
392
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
393
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
394
+ log_entry
395
+ else
396
+ "%s %s" % [message, dump]
397
+ end
398
+ end
399
+ end
400
+
401
+ class TableDefinition
402
+
403
+ def initialize
404
+ @column_families = []
405
+ end
406
+
407
+ # Returns a column family for the column with name +name+.
408
+ def [](name)
409
+ @column_families.find {|column| column.name.to_s == name.to_s}
410
+ end
411
+
412
+ def column_family(name, options = {})
413
+ column = self[name] || BigRecordDriver::ColumnDescriptor.new(name.to_s, options)
414
+
415
+ @column_families << column unless @column_families.include? column
416
+ self
417
+ end
418
+
419
+ alias :family :column_family
420
+
421
+ def to_adapter_format
422
+ @column_families
423
+ end
424
+
425
+ def column_families_list
426
+ @column_families.map(&:name).join(", ")
427
+ end
428
+
429
+ end
430
+
431
+ end
432
+ end
@@ -0,0 +1,27 @@
1
+ module BigRecord
2
+ module ConnectionAdapters
3
+ class View
4
+ attr_reader :name, :owner
5
+
6
+ def initialize(name, column_names, owner)
7
+ @name = name.to_s
8
+ @column_names = column_names ? column_names.collect{|c| c.to_s} : nil
9
+ @owner = owner
10
+ end
11
+
12
+ # Return the column objects associated with this view. By default the views 'all' and 'default' return every column.
13
+ def columns
14
+ if @column_names
15
+ @column_names.collect{|cn| owner.columns_hash[cn]}
16
+ else
17
+ owner.columns
18
+ end
19
+ end
20
+
21
+ # Return the name of the column objects associated with this view. By default the views 'all' and 'default' return every column.
22
+ def column_names
23
+ @column_names || owner.column_names
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,10 @@
1
+ dir = File.expand_path(File.join(File.dirname(__FILE__), "connection_adapters"))
2
+
3
+ require dir + '/column'
4
+ require dir + '/view'
5
+ require dir + '/abstract/database_statements'
6
+ require dir + '/abstract/quoting'
7
+ require dir + '/abstract/connection_specification'
8
+
9
+ require dir + '/abstract_adapter'
10
+ require dir + '/hbase_adapter'
@@ -0,0 +1,73 @@
1
+ module BigRecord
2
+ module Deletion
3
+ def self.included(base) #:nodoc:
4
+ base.alias_method_chain :destroy_without_callbacks, :flag_deleted
5
+ base.extend ClassMethods
6
+
7
+ base.class_eval do
8
+ class << self
9
+ alias_method_chain :find_one, :flag_deleted
10
+ alias_method_chain :find_every, :flag_deleted
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ # Flag the record as "deleted" if it responds to "deleted", else destroy it
17
+ def destroy_without_callbacks_with_flag_deleted #:nodoc:
18
+ if self.respond_to?(:deleted)
19
+ # mark as deleted
20
+ self.deleted = true
21
+
22
+ # set the timestamp
23
+ if record_timestamps
24
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
25
+ self.send(:updated_at=, t) if respond_to?(:updated_at)
26
+ self.send(:updated_on=, t) if respond_to?(:updated_on)
27
+ end
28
+
29
+ self.update_without_callbacks
30
+ else
31
+ destroy_without_callbacks_without_flag_deleted
32
+ end
33
+ end
34
+
35
+ module ClassMethods
36
+ def find_one_with_flag_deleted(*args)
37
+ options = args.last.is_a?(Hash) ? args.last : {}
38
+ records = find_one_without_flag_deleted(*args)
39
+ unless options[:include_deleted]
40
+ if records.is_a?(Array)
41
+ records.each{|record| check_not_deleted(record)}
42
+ else
43
+ check_not_deleted(records)
44
+ end
45
+ end
46
+ records
47
+ end
48
+
49
+ def find_every_with_flag_deleted(*args)
50
+ options = args.last.is_a?(Hash) ? args.last : {}
51
+ records = find_every_without_flag_deleted(*args)
52
+
53
+ unless options[:include_deleted]
54
+ records.select do |record|
55
+ begin
56
+ check_not_deleted(record)
57
+ true
58
+ rescue
59
+ false
60
+ end
61
+ end
62
+ else
63
+ records
64
+ end
65
+ end
66
+
67
+ def check_not_deleted(record)
68
+ raise BigRecord::RecordNotFound, "The record (id=#{record.id}) is marked as deleted." if record.respond_to?(:deleted) and record.deleted
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,92 @@
1
+ # Replace the anonymous classes
2
+ module BigRecord
3
+ module DynamicSchema
4
+
5
+ def self.included(base) #:nodoc:
6
+ super
7
+
8
+ base.alias_method_chain :column_for_attribute, :dynamic_schema
9
+ base.alias_method_chain :attributes_from_column_definition, :dynamic_schema
10
+ base.alias_method_chain :inspect, :dynamic_schema
11
+ base.alias_method_chain :define_read_methods, :dynamic_schema
12
+
13
+ end
14
+
15
+ # Stub of the callback for setting the dynamic columns. Override this to add dynamic columns
16
+ def initialize_columns(options={})
17
+
18
+ end
19
+
20
+ # Create and add a dynamic column to this record
21
+ def dynamic_column(name, type, options={})
22
+ add_dynamic_column ConnectionAdapters::Column.new(name.to_s, type, options)
23
+ end
24
+
25
+ # Add an existing dynamic column to this record
26
+ def add_dynamic_column(c)
27
+ columns_hash[c.name] = c
28
+ @columns_name= nil; @columns= nil #reset
29
+ c
30
+ end
31
+
32
+ def columns_hash
33
+ unless @columns_hash
34
+ @columns_hash = self.class.columns_hash.dup
35
+ initialize_columns
36
+ end
37
+ @columns_hash
38
+ end
39
+
40
+ def columns
41
+ @columns ||= columns_hash.values
42
+ end
43
+
44
+ def column_names
45
+ @column_names ||= columns_hash.keys
46
+ end
47
+
48
+ # Returns the column object for the named attribute.
49
+ def column_for_attribute_with_dynamic_schema(name)
50
+ self.columns_hash[name.to_s]
51
+ end
52
+
53
+ # Initializes the attributes array with keys matching the columns from the linked table and
54
+ # the values matching the corresponding default value of that column, so
55
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
56
+ # that instances loaded from the database would.
57
+ def attributes_from_column_definition_with_dynamic_schema
58
+ self.columns.inject({}) do |attributes, column|
59
+ unless column.name == self.class.primary_key
60
+ attributes[column.name] = column.default
61
+ end
62
+ attributes
63
+ end
64
+ end
65
+
66
+ # Returns the contents of the record as a nicely formatted string.
67
+ def inspect_with_dynamic_schema
68
+ attributes_as_nice_string = self.column_names.collect { |name|
69
+ if has_attribute?(name) || new_record?
70
+ "#{name}: #{attribute_for_inspect(name)}"
71
+ end
72
+ }.compact.join(", ")
73
+ "#<#{self.class} #{attributes_as_nice_string}>"
74
+ end
75
+
76
+ # Called on first read access to any given column and generates reader
77
+ # methods for all columns in the columns_hash if
78
+ # ActiveRecord::Base.generate_read_methods is set to true.
79
+ def define_read_methods_with_dynamic_schema
80
+ columns_hash.each do |name, column|
81
+ unless respond_to_without_attributes?(name)
82
+ define_read_method(name.to_sym, name, column)
83
+ end
84
+
85
+ unless respond_to_without_attributes?("#{name}?")
86
+ define_question_method(name)
87
+ end
88
+ end
89
+ end
90
+
91
+ end
92
+ end
@@ -0,0 +1,71 @@
1
+ module BigRecord
2
+ class Embedded < AbstractBase
3
+
4
+ def initialize(attrs = nil)
5
+ super
6
+ # Regenerate the id unless it's already there (i.e. we're instantiating an existing property)
7
+ @attributes["id"] ||= generate_id
8
+ end
9
+
10
+ def connection
11
+ self.class.connection
12
+ end
13
+
14
+ def id
15
+ super || (self.id = generate_id)
16
+ end
17
+
18
+ protected
19
+ def generate_id
20
+ UUIDTools::UUID.random_create.to_s
21
+ end
22
+
23
+ public
24
+ class << self
25
+ def store_primary_key?
26
+ true
27
+ end
28
+
29
+ def primary_key
30
+ "id"
31
+ end
32
+
33
+ # Borrow the default connection of BigRecord
34
+ def connection
35
+ BigRecord::Base.connection
36
+ end
37
+
38
+ def base_class
39
+ (superclass == BigRecord::Embedded) ? self : superclass.base_class
40
+ end
41
+
42
+ # Class attribute that holds the name of the embedded type for dispaly
43
+ def pretty_name
44
+ @pretty_name || self.to_s
45
+ end
46
+
47
+ def set_pretty_name new_name
48
+ @pretty_name = new_name
49
+ end
50
+
51
+ def hide_to_users
52
+ @hide_to_user = true
53
+ end
54
+
55
+ def show_to_users?
56
+ !@hide_to_user
57
+ end
58
+
59
+ def inherited(child) #:nodoc:
60
+ child.set_pretty_name child.name.split("::").last
61
+ super
62
+ end
63
+
64
+ def default_columns
65
+ {primary_key => ConnectionAdapters::Column.new(primary_key, 'string')}
66
+ end
67
+
68
+ end
69
+
70
+ end
71
+ end