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,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