bigrecord 0.0.7 → 0.0.8

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 (107) hide show
  1. data/LICENSE +0 -0
  2. data/README.rdoc +9 -1
  3. data/Rakefile +0 -0
  4. data/VERSION +1 -1
  5. data/examples/bigrecord.yml +2 -5
  6. data/generators/bigrecord/bigrecord_generator.rb +0 -0
  7. data/generators/bigrecord/templates/bigrecord.rake +0 -0
  8. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +0 -0
  9. data/generators/bigrecord_migration/templates/migration.rb +0 -0
  10. data/generators/bigrecord_model/bigrecord_model_generator.rb +0 -0
  11. data/generators/bigrecord_model/templates/migration.rb +0 -0
  12. data/generators/bigrecord_model/templates/model.rb +0 -0
  13. data/generators/bigrecord_model/templates/model_spec.rb +0 -0
  14. data/{doc → guides}/bigrecord_specs.rdoc +0 -0
  15. data/guides/deployment.rdoc +21 -0
  16. data/{doc → guides}/getting_started.rdoc +30 -18
  17. data/init.rb +0 -0
  18. data/install.rb +0 -0
  19. data/lib/big_record/abstract_base.rb +50 -28
  20. data/lib/big_record/action_view_extensions.rb +1 -1
  21. data/lib/big_record/ar_associations/association_collection.rb +0 -0
  22. data/lib/big_record/ar_associations/association_proxy.rb +0 -0
  23. data/lib/big_record/ar_associations/belongs_to_association.rb +0 -0
  24. data/lib/big_record/ar_associations/belongs_to_many_association.rb +0 -0
  25. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +0 -0
  26. data/lib/big_record/ar_associations/has_many_association.rb +0 -0
  27. data/lib/big_record/ar_associations/has_one_association.rb +0 -0
  28. data/lib/big_record/ar_associations.rb +0 -0
  29. data/lib/big_record/ar_reflection.rb +0 -0
  30. data/lib/big_record/attribute_methods.rb +0 -0
  31. data/lib/big_record/base.rb +49 -24
  32. data/lib/big_record/br_associations/association_collection.rb +0 -0
  33. data/lib/big_record/br_associations/association_proxy.rb +0 -0
  34. data/lib/big_record/br_associations/belongs_to_association.rb +0 -0
  35. data/lib/big_record/br_associations/belongs_to_many_association.rb +0 -0
  36. data/lib/big_record/br_associations/cached_item_proxy.rb +0 -0
  37. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +0 -0
  38. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +0 -0
  39. data/lib/big_record/br_associations/has_one_association.rb +0 -0
  40. data/lib/big_record/br_associations.rb +0 -0
  41. data/lib/big_record/br_reflection.rb +0 -0
  42. data/lib/big_record/callbacks.rb +0 -0
  43. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +0 -0
  44. data/lib/big_record/connection_adapters/abstract/database_statements.rb +0 -0
  45. data/lib/big_record/connection_adapters/abstract/quoting.rb +1 -1
  46. data/lib/big_record/connection_adapters/abstract_adapter.rb +0 -0
  47. data/lib/big_record/connection_adapters/column.rb +30 -0
  48. data/lib/big_record/connection_adapters/hbase_adapter.rb +0 -0
  49. data/lib/big_record/connection_adapters/hbase_rest_adapter.rb +424 -0
  50. data/lib/big_record/connection_adapters/view.rb +63 -0
  51. data/lib/big_record/connection_adapters.rb +1 -0
  52. data/lib/big_record/deletion.rb +0 -0
  53. data/lib/big_record/dynamic_schema.rb +0 -0
  54. data/lib/big_record/embedded.rb +128 -1
  55. data/lib/big_record/embedded_associations/association_proxy.rb +0 -0
  56. data/lib/big_record/family_span_columns.rb +0 -0
  57. data/lib/big_record/fixtures.rb +0 -0
  58. data/lib/big_record/migration.rb +30 -8
  59. data/lib/big_record/routing_ext.rb +6 -4
  60. data/lib/big_record/timestamp.rb +0 -0
  61. data/lib/big_record/validations.rb +0 -0
  62. data/lib/big_record.rb +5 -3
  63. data/lib/bigrecord.rb +0 -0
  64. data/rails/init.rb +3 -2
  65. data/spec/adapter_benchmark.rb +55 -0
  66. data/spec/connections/bigrecord.yml +4 -2
  67. data/spec/connections/cassandra/connection.rb +0 -0
  68. data/spec/connections/hbase/connection.rb +0 -0
  69. data/spec/debug.log +156 -40724
  70. data/spec/integration/br_associations_spec.rb +0 -0
  71. data/spec/lib/animal.rb +0 -0
  72. data/spec/lib/book.rb +0 -0
  73. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +0 -0
  74. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +0 -0
  75. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +0 -0
  76. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +0 -0
  77. data/spec/lib/company.rb +0 -0
  78. data/spec/lib/embedded/web_link.rb +0 -0
  79. data/spec/lib/employee.rb +0 -0
  80. data/spec/lib/migrations/20090706182535_add_animals_table.rb +0 -0
  81. data/spec/lib/migrations/20090706190623_add_books_table.rb +0 -0
  82. data/spec/lib/migrations/20090706193019_add_companies_table.rb +0 -0
  83. data/spec/lib/migrations/20090706194512_add_employees_table.rb +0 -0
  84. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +0 -0
  85. data/spec/lib/novel.rb +2 -0
  86. data/spec/lib/zoo.rb +0 -0
  87. data/spec/spec.opts +0 -0
  88. data/spec/spec_helper.rb +7 -1
  89. data/spec/unit/abstract_base_spec.rb +0 -0
  90. data/spec/unit/adapters/abstract_adapter_spec.rb +0 -0
  91. data/spec/unit/adapters/adapter_shared_spec.rb +0 -0
  92. data/spec/unit/adapters/hbase_adapter_spec.rb +0 -0
  93. data/spec/unit/ar_associations_spec.rb +0 -0
  94. data/spec/unit/base_spec.rb +0 -0
  95. data/spec/unit/br_associations_spec.rb +0 -0
  96. data/spec/unit/embedded_spec.rb +0 -0
  97. data/spec/unit/find_spec.rb +0 -0
  98. data/spec/unit/hash_helper_spec.rb +0 -0
  99. data/spec/unit/migration_spec.rb +0 -0
  100. data/spec/unit/model_spec.rb +0 -0
  101. data/spec/unit/validations_spec.rb +0 -0
  102. data/tasks/bigrecord_tasks.rake +0 -0
  103. data/tasks/data_store.rb +8 -1
  104. data/tasks/gem.rb +2 -2
  105. data/tasks/rdoc.rb +14 -8
  106. data/tasks/spec.rb +0 -0
  107. metadata +10 -6
@@ -2,7 +2,7 @@ module BigRecord
2
2
  module ConnectionAdapters # :nodoc:
3
3
  module Quoting
4
4
  # Quotes the column value to help prevent
5
- # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
5
+ # SQL injection attacks [http://en.wikipedia.org/wiki/SQL_injection].
6
6
  def quote(value, column = nil)
7
7
  # records are quoted as their primary key
8
8
  return value.quoted_id if value.respond_to?(:quoted_id)
@@ -4,6 +4,36 @@ require 'bigdecimal/util'
4
4
 
5
5
  module BigRecord
6
6
  module ConnectionAdapters #:nodoc:
7
+
8
+ # = Column/Attribute Definition
9
+ #
10
+ # As long as a model has at least one column family set up for it, then
11
+ # columns (a.k.a. model attributes) can then be defined for the model.
12
+ #
13
+ # The following is an example of a model named book.rb that has a
14
+ # column family called "attribute" set up for it:
15
+ #
16
+ # class Book < BigRecord::Base
17
+ # column 'attribute:title', :string
18
+ # column :author, :string
19
+ # column :description, :string
20
+ # column :links, :string, :collection => true
21
+ # end
22
+ #
23
+ # This simple model defines 4 columns of type string. An important thing
24
+ # to notice here is that the first column 'attribute:title' has the column
25
+ # family prepended to it. This is identical to just passing the symbol
26
+ # :title to the column method, and the default behaviour is to prepend
27
+ # the column family (attribute) automatically if one is not defined.
28
+ #
29
+ # Furthermore, in HBase, there's the option of storing collections for a
30
+ # given column. This will return an array for the links attribute on a
31
+ # Book record.
32
+ #
33
+ # == Types and Options
34
+ #
35
+ # @see BigRecord::AbstractBase.column
36
+ #
7
37
  class Column
8
38
  module Format
9
39
  ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
File without changes
@@ -0,0 +1,424 @@
1
+ require 'set'
2
+ require 'hbase'
3
+
4
+ module BigRecord
5
+ class Base
6
+ # Establishes a connection to the database that's used by all Active Record objects.
7
+ def self.hbase_rest_connection(config) # :nodoc:
8
+ config = config.symbolize_keys
9
+
10
+ api_address = config[:api_address]
11
+
12
+ hbase = HBase::Client.new(api_address)
13
+
14
+ ConnectionAdapters::HbaseRestAdapter.new(hbase, logger, [], config)
15
+ end
16
+ end
17
+
18
+ module ConnectionAdapters
19
+ class HbaseRestAdapter < AbstractAdapter
20
+ @@emulate_booleans = true
21
+ cattr_accessor :emulate_booleans
22
+
23
+ LOST_CONNECTION_ERROR_MESSAGES = [
24
+ "Server shutdown in progress",
25
+ "Broken pipe",
26
+ "Lost connection to HBase server during query",
27
+ "HBase server has gone away"
28
+ ]
29
+
30
+ # data types
31
+ TYPE_NULL = 0x00;
32
+ TYPE_STRING = 0x01; # utf-8 strings
33
+ TYPE_BOOLEAN = 0x04; # delegate to YAML
34
+ TYPE_BINARY = 0x07; # byte[] => no conversion
35
+
36
+ # string charset
37
+ CHARSET = "utf-8"
38
+
39
+ # utility constants
40
+ NULL = "\000"
41
+
42
+ def initialize(connection, logger, connection_options, config)
43
+ super(connection, logger)
44
+ @connection_options, @config = connection_options, config
45
+
46
+ connect
47
+ end
48
+
49
+ def configuration
50
+ @config.clone
51
+ end
52
+
53
+ def adapter_name #:nodoc:
54
+ 'HBase-Rest'
55
+ end
56
+
57
+ def supports_migrations? #:nodoc:
58
+ true
59
+ end
60
+
61
+ # CONNECTION MANAGEMENT ====================================
62
+
63
+ def active?
64
+ true
65
+ end
66
+
67
+ def reconnect!
68
+ end
69
+
70
+ def disconnect!
71
+ end
72
+
73
+
74
+ # DATABASE STATEMENTS ======================================
75
+
76
+ def columns_to_hbase_format(data = {})
77
+ # Convert it to the hbase-ruby format
78
+ # TODO: Add this function to hbase-ruby instead.
79
+ data.map{|col, content| {:name => col.to_s, :value => content}}.compact
80
+ end
81
+
82
+ def update_raw(table_name, row, values, timestamp)
83
+ result = nil
84
+
85
+ columns = columns_to_hbase_format(values)
86
+ timestamp = Time.now.to_bigrecord_timestamp
87
+
88
+ log "UPDATE #{table_name} SET #{values.inspect if values} WHERE ROW=#{row};" do
89
+ @connection.create_row(table_name, row, timestamp, columns)
90
+ end
91
+ result
92
+ end
93
+
94
+ def update(table_name, row, values, timestamp)
95
+ serialized_collection = {}
96
+ values.each do |column, value|
97
+ serialized_collection[column] = serialize(value)
98
+ end
99
+ update_raw(table_name, row, serialized_collection, timestamp)
100
+ end
101
+
102
+ def get_raw(table_name, row, column, options={})
103
+ result = nil
104
+ timestamp = options[:timestamp] || nil
105
+ log "SELECT (#{column}) FROM #{table_name} WHERE ROW=#{row};" do
106
+ columns = @connection.show_row(table_name, row, timestamp, column, options).columns
107
+
108
+ result = (columns.size == 1) ? columns.first.value : columns.map(&:value)
109
+ end
110
+ result
111
+ end
112
+
113
+ def get(table_name, row, column, options={})
114
+ serialized_result = get_raw(table_name, row, column, options)
115
+ result = nil
116
+ if serialized_result.is_a?(Array)
117
+ result = serialized_result.collect{|e| deserialize(e)}
118
+ else
119
+ result = deserialize(serialized_result)
120
+ end
121
+ result
122
+ end
123
+
124
+ def get_columns_raw(table_name, row, columns = nil, options={})
125
+ result = {}
126
+
127
+ timestamp = options[:timestamp] || nil
128
+
129
+ log "SELECT (#{columns.join(", ")}) FROM #{table_name} WHERE ROW=#{row};" do
130
+ row = @connection.show_row(table_name, row, timestamp, columns, options)
131
+ result.merge!({'id' => row.name})
132
+ columns = row.columns
133
+ columns.each{ |col| result.merge!({col.name => col.value}) }
134
+ end
135
+ result
136
+ end
137
+
138
+ def get_columns(table_name, row, columns, options={})
139
+ row_cols = get_columns_raw(table_name, row, columns, options)
140
+ return nil unless row_cols
141
+
142
+ result = {}
143
+ row_cols.each do |key,value|
144
+ begin
145
+ result[key] =
146
+ if key == 'id'
147
+ value
148
+ else
149
+ deserialize(value)
150
+ end
151
+ rescue Exception => e
152
+ puts "Could not load column value #{key} for row=#{row.name}"
153
+ end
154
+ end
155
+ result
156
+ end
157
+
158
+ def get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row = nil)
159
+ result = nil
160
+ log "SCAN (#{columns.join(", ")}) FROM #{table_name} WHERE START_ROW=#{start_row} AND STOP_ROW=#{stop_row} LIMIT=#{limit};" do
161
+ options = {:start_row => start_row, :end_row => stop_row, :columns => columns}
162
+ scanner = @connection.open_scanner(table_name, options)
163
+ result = @connection.get_rows(scanner, limit)
164
+ @connection.close_scanner(scanner)
165
+ end
166
+ result
167
+ end
168
+
169
+ def get_consecutive_rows(table_name, start_row, limit, columns, stop_row = nil)
170
+ rows = get_consecutive_rows_raw(table_name, start_row, limit, columns, stop_row)
171
+
172
+ result = rows.collect do |row|
173
+ cols = {}
174
+ row.columns.each do |col|
175
+ key = col.name
176
+ value = col.value
177
+ begin
178
+ cols[key] =
179
+ if key == 'id'
180
+ value
181
+ else
182
+ deserialize(value)
183
+ end
184
+ rescue Exception => e
185
+ puts "Could not load column value #{key} for row=#{row.name}"
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_row(table_name, row, timestamp)
198
+ end
199
+ result
200
+ end
201
+
202
+ def truncate_table(table_name)
203
+ end
204
+
205
+
206
+ # SCHEMA STATEMENTS ========================================
207
+
208
+ def initialize_schema_migrations_table
209
+ sm_table = BigRecord::Migrator.schema_migrations_table_name
210
+
211
+ unless table_exists?(sm_table)
212
+ create_table(sm_table) do |t|
213
+ t.family :attribute, :versions => 1
214
+ end
215
+ end
216
+ end
217
+
218
+ def get_all_schema_versions
219
+ sm_table = BigRecord::Migrator.schema_migrations_table_name
220
+
221
+ get_consecutive_rows(sm_table, nil, nil, ["attribute:version"]).map{|version| version["attribute:version"]}
222
+ end
223
+
224
+ def table_exists?(table_name)
225
+ log "TABLE EXISTS? #{table_name};" do
226
+ @connection.list_tables.map(&:name).include?(table_name)
227
+ end
228
+ end
229
+
230
+ def create_table(table_name, options = {})
231
+ table_definition = TableDefinition.new
232
+
233
+ yield table_definition if block_given?
234
+
235
+ if options[:force] && table_exists?(table_name)
236
+ drop_table(table_name)
237
+ end
238
+
239
+ result = nil
240
+ log "CREATE TABLE #{table_name} (#{table_definition.column_families_list});" do
241
+ result = @connection.create_table(table_name, *table_definition.to_adapter_format)
242
+ end
243
+ result
244
+ end
245
+
246
+ def drop_table(table_name)
247
+ result = nil
248
+ log "DROP TABLE #{table_name};" do
249
+ result = @connection.destroy_table(table_name)
250
+ end
251
+ result
252
+ end
253
+
254
+ def add_column_family(table_name, column_name, options = {})
255
+ end
256
+
257
+ alias :add_family :add_column_family
258
+
259
+ def remove_column_family(table_name, column_name)
260
+ end
261
+
262
+ alias :remove_family :remove_column_family
263
+
264
+ def modify_column_family(table_name, column_name, options = {})
265
+ end
266
+
267
+ alias :modify_family :modify_column_family
268
+
269
+ # Serialize the given value
270
+ def serialize(value)
271
+ case value
272
+ when NilClass then NULL
273
+ when String then build_serialized_value(TYPE_STRING, value)
274
+ else value.to_yaml
275
+ end
276
+ end
277
+
278
+ # Serialize an object in a given type
279
+ def build_serialized_value(type, value)
280
+ type.chr + value
281
+ end
282
+
283
+ # Deserialize the given string. This method supports both the pure YAML format and
284
+ # the type header format.
285
+ def deserialize(str)
286
+ return unless str
287
+
288
+ # stay compatible with the old serialization code
289
+ # YAML documents start with "--- " so if we find that sequence at the beginning we
290
+ # consider it as a serialized YAML value, else it's the new format with the type header
291
+ if str[0..3] == "--- "
292
+ YAML::load(str) if str
293
+ else
294
+ deserialize_with_header(str)
295
+ end
296
+ end
297
+
298
+ # Deserialize the given string assumed to be in the type header format.
299
+ def deserialize_with_header(data)
300
+ return unless data and data.size >= 2
301
+
302
+ # the type of the data is encoded in the first byte
303
+ type = data[0];
304
+
305
+ case type
306
+ when TYPE_NULL then nil
307
+ when TYPE_STRING then data[1..-1]
308
+ when TYPE_BINARY then data[1..-1]
309
+ else nil
310
+ end
311
+ end
312
+
313
+ private
314
+
315
+ def connect
316
+ end
317
+
318
+ protected
319
+ def log(str, name = nil)
320
+ if block_given?
321
+ if @logger and @logger.level <= Logger::INFO
322
+ result = nil
323
+ seconds = Benchmark.realtime { result = yield }
324
+ @runtime += seconds
325
+ log_info(str, name, seconds)
326
+ result
327
+ else
328
+ yield
329
+ end
330
+ else
331
+ log_info(str, name, 0)
332
+ nil
333
+ end
334
+ rescue Exception => e
335
+ # Log message and raise exception.
336
+ # Set last_verfication to 0, so that connection gets verified
337
+ # upon reentering the request loop
338
+ @last_verification = 0
339
+ message = "#{e.class.name}: #{e.message}: #{str}"
340
+ log_info(message, name, 0)
341
+ raise e
342
+ end
343
+
344
+ def log_info(str, name, runtime)
345
+ return unless @logger
346
+
347
+ @logger.debug(
348
+ format_log_entry(
349
+ "#{name.nil? ? "HBASE" : name} (#{sprintf("%f", runtime)})",
350
+ str.gsub(/ +/, " ")
351
+ )
352
+ )
353
+ end
354
+
355
+ def format_log_entry(message, dump = nil)
356
+ if BigRecord::Base.colorize_logging
357
+ if @@row_even
358
+ @@row_even = false
359
+ message_color, dump_color = "4;36;1", "0;1"
360
+ else
361
+ @@row_even = true
362
+ message_color, dump_color = "4;35;1", "0"
363
+ end
364
+
365
+ log_entry = " \e[#{message_color}m#{message}\e[0m "
366
+ log_entry << "\e[#{dump_color}m%#{String === dump ? 's' : 'p'}\e[0m" % dump if dump
367
+ log_entry
368
+ else
369
+ "%s %s" % [message, dump]
370
+ end
371
+ end
372
+ end
373
+
374
+ class TableDefinition
375
+
376
+ ##
377
+ # Given an column descriptor's options hash from Bigrecord, translate it into
378
+ # the format for this adapter's ColumnDescriptor.
379
+ #
380
+ def self.translate_to_adapter_format(options)
381
+ # Translating to the hbase-ruby column descriptor
382
+ # TODO: Refactor this
383
+ hbase_params = {}
384
+
385
+ hbase_params[:name] = options[:name].to_s if options[:name]
386
+ hbase_params[:compression] = options[:compression] if options[:compression]
387
+ hbase_params[:max_versions] = options[:versions] if options[:versions]
388
+ hbase_params[:bloomfilter] = options[:bloom_filter] if options[:bloom_filter]
389
+
390
+ hbase_params
391
+ end
392
+
393
+ def initialize
394
+ @column_families = []
395
+ end
396
+
397
+ # Returns a column family for the column with name +name+.
398
+ def [](name)
399
+ @column_families.find {|column| column[:name].to_s == name.to_s}
400
+ end
401
+
402
+ def column_family(name, options = {})
403
+ hbase_params = self.class.translate_to_adapter_format(options.merge({:name => name}))
404
+
405
+ column = self[name] || hbase_params
406
+
407
+ @column_families << column unless @column_families.include? column
408
+ self
409
+ end
410
+
411
+ alias :family :column_family
412
+
413
+ def to_adapter_format
414
+ @column_families
415
+ end
416
+
417
+ def column_families_list
418
+ @column_families.map{|x| x[:name]}.join(", ")
419
+ end
420
+
421
+ end
422
+
423
+ end
424
+ end
@@ -1,5 +1,68 @@
1
1
  module BigRecord
2
2
  module ConnectionAdapters
3
+
4
+ # = Named Views
5
+ #
6
+ # To use column-oriented databases more efficiently, it helps to know
7
+ # exactly which specific columns are required for a query.
8
+ #
9
+ # For example, supposing we're designing two queries:
10
+ # (A) used on the front page of a website.
11
+ # (B) used on the summary page for a particular item.
12
+ #
13
+ # Since we know these two queries will be serving up different information,
14
+ # we can also figure out which columns are needed by each. In general,
15
+ # query (A) would only require a subset of columns of (B). This
16
+ # functionality is analogous to the SQL command "SELECT column1,column2 ..."
17
+ # for defining specific columns to query for.
18
+ #
19
+ # Named views are defined in models by using the {BigRecord::Base.view}
20
+ # macro like in the following example:
21
+ #
22
+ # class Book < BigRecord::Base
23
+ # column :title, :string
24
+ # column :author, :string
25
+ # column :description, :string
26
+ # column :links, :string, :collection => true
27
+ #
28
+ # view :front_page, :title, :author
29
+ # view :summary_page, :title, :author, :description
30
+ #
31
+ # # Override default if you don't want all columns returned
32
+ # view :default, :title, :author, :description
33
+ # end
34
+ #
35
+ # Now, whenever you work with a Book record, it will only returned the
36
+ # columns you specify according to the view option you pass. i.e.
37
+ #
38
+ # >> Book.find(:first, :view => :front_page)
39
+ # => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:title: "Hello Thar", attribute:author: "Greg">
40
+ #
41
+ # >> Book.find(:first, :view => :summary_page)
42
+ # => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:title: "Hello Thar", attribute:author: "Greg">
43
+ #
44
+ # >> Book.find(:first, :view => :default)
45
+ # => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:title: "Hello Thar", attribute:links: ["link1", "link2", "link3", "link4"], attribute:author: "Greg">
46
+ #
47
+ # Any attributes that were not loaded in as part of the view will be
48
+ # lazy-loaded on request. This is very inefficient and should be avoided
49
+ # at all costs! It's better to load more columns in, than to have
50
+ # any attributes lazy-loaded afterwards.
51
+ #
52
+ # Note: A Bigrecord model will return all the columns within the default
53
+ # column family (when :view option is left blank, for example).
54
+ # You can override the :default name view to change this behaviour.
55
+ #
56
+ # It's also possible to define specific columns to load at query time.
57
+ # To do this, you can just pass an array of columns to the :columns
58
+ # option of the find method and it will return only those attributes:
59
+ #
60
+ # >> Book.find(:first, :columns => [:author, :description])
61
+ # => #<Book id: "2e13f182-1085-495e-9841-fe5c84ae9992", attribute:description: "Masterpiece!", attribute:author: "Greg">
62
+ #
63
+ # Once again, any attributes not loaded during query time will be
64
+ # lazy-loaded on request.
65
+ #
3
66
  class View
4
67
  attr_reader :name, :owner
5
68
 
@@ -8,3 +8,4 @@ require dir + '/abstract/connection_specification'
8
8
 
9
9
  require dir + '/abstract_adapter'
10
10
  require dir + '/hbase_adapter'
11
+ require dir + '/hbase_rest_adapter'
File without changes
File without changes
@@ -1,9 +1,134 @@
1
1
  module BigRecord
2
+
3
+ # = Embedded Records
4
+ #
5
+ # Since a single column in a column-oriented database is perfectly suited
6
+ # to handle large amounts of data, BigRecord gives you the option to store
7
+ # entire records within a single column of another {BigRecord::Base} record.
8
+ #
9
+ # These are known as Embedded records, and they behave similarly to
10
+ # {BigRecord::Base} objects, except that their data is physically stored
11
+ # within another {BigRecord::Base} record, and they don't exist unless
12
+ # associated with one. Furthermore, they don't possess any find or querying
13
+ # functionality.
14
+ #
15
+ # So what are the benefits of Embedded records?
16
+ # * Cleaner organization of models
17
+ # * Avoids the need to create entire tables and associations for models that
18
+ # exist only in the context of another model.
19
+ # * Allows more complicated functionality to be encompassed within an
20
+ # embedded record, instead of in a parent model.
21
+ #
22
+ # All of this has been very abstract so far, therefore examples are in order.
23
+ #
24
+ # == Examples
25
+ #
26
+ # Let's say we start off with the following arbitrarily created models:
27
+ #
28
+ # app/model/book.rb:
29
+ # class Book < BigRecord::Base
30
+ # column :title, :string
31
+ # column :author, :string
32
+ # column :description, :string
33
+ # end
34
+ #
35
+ # app/model/company.rb:
36
+ # class Company < BigRecord::Base
37
+ # column :name, :string
38
+ # column :address, :string
39
+ # column :description, :string
40
+ # end
41
+ #
42
+ # Now, let's say we want the ability to create and associate weblinks
43
+ # with each of these models. This is a trivial modification if all we do is
44
+ # create a new column in each model called "weblink" (or something similar),
45
+ # and have a string that stores a URL.
46
+ #
47
+ # However, what if we wanted these weblinks to have more metadata attached
48
+ # and more complex functionality added to it? Then our only choice is to
49
+ # create a new model called WebLink (for example), with its own table, set of
50
+ # attributes and methods. Then we have our Book and Company models associate
51
+ # to these newly created WebLink models, giving us something like this:
52
+ #
53
+ # app/model/web_link.rb:
54
+ # class WebLink < BigRecord::Base
55
+ # column :name, :string
56
+ # column :url, :string
57
+ # column :description, :string
58
+ # column :submitted_by, :string
59
+ # column :book_id, :string
60
+ # column :company_id, :string
61
+ #
62
+ # # Could use a polymorphic association here, of course.
63
+ # belongs_to_bigrecord :book, :foreign_key => "attribute:book_id"
64
+ # belongs_to_bigrecord :company, :foreign_key => "attribute:company_id"
65
+ #
66
+ # # other methods ...
67
+ # end
68
+ #
69
+ # and likewise an association to WebLink from the Book and Company models.
70
+ #
71
+ # Now notice the problem here? A simple concept like adding a WebLink with
72
+ # some metadata has increased the model logic and created some unnecessary
73
+ # associations. In this situation, a WebLink doesn't need to exist except
74
+ # when associated to a certain model. Therefore this association should be
75
+ # implicit somehow.
76
+ #
77
+ # Enter Embedded records. We will now instead define WebLink as follows:
78
+ #
79
+ # app/model/web_link.rb:
80
+ # class WebLink < BigRecord::Embedded
81
+ # column :name, :string
82
+ # column :url, :string
83
+ # column :description, :string
84
+ # column :submitted_by, :string
85
+ #
86
+ # # other methods ...
87
+ # end
88
+ #
89
+ # And modify our Base records like so:
90
+ #
91
+ # app/model/book.rb:
92
+ # class Book < BigRecord::Base
93
+ # column :title, :string
94
+ # column :author, :string
95
+ # column :description, :string
96
+ # column :web_link, 'WebLink'
97
+ # end
98
+ #
99
+ # app/model/company.rb:
100
+ # class Company < BigRecord::Base
101
+ # column :name, :string
102
+ # column :address, :string
103
+ # column :description, :string
104
+ # column :web_link, 'WebLink'
105
+ # end
106
+ #
107
+ # Now we can encompass any WebLink specific attributes and methods within
108
+ # the WebLink embedded class, and use them with any other Base model.
109
+ #
110
+ # To use WebLink now, we treat it as though it were a regular model, except
111
+ # that we don't execute saves on it. For example:
112
+ #
113
+ # >> amazon_link = WebLink.new(:name => "Amazon Link to Book", :url => "http://amazon.com/some/book", :description => "Amazon sells this book for cheap!")
114
+ # => #<WebLink created_at: "2009-11-12 17:11:57", name: "Amazon Link to Book", url: "http://amazon.com/some/book", description: "Amazon sells this book for cheap!", submitted_by: nil, id: "2b619a68-e462-475d-8e04-01ba2aace11a">
115
+ # >> amazon_link.save
116
+ # BigRecord::NotImplemented: BigRecord::NotImplemented
117
+ # # => [...]
118
+ # >> book = Book.find(:first)
119
+ # # => [...]
120
+ # >> book.web_link = amazon_link
121
+ # >> book.save
122
+ #
123
+ # Now any subsequent access to that book object we just saved to will have
124
+ # a WebLink record available with it.
125
+ #
2
126
  class Embedded < AbstractBase
3
127
 
4
128
  def initialize(attrs = nil)
5
129
  super
6
- # Regenerate the id unless it's already there (i.e. we're instantiating an existing property)
130
+ # Regenerate the id unless it's already there
131
+ # (i.e. we're instantiating an existing property)
7
132
  @attributes["id"] ||= generate_id
8
133
  end
9
134
 
@@ -16,11 +141,13 @@ module BigRecord
16
141
  end
17
142
 
18
143
  protected
144
+
19
145
  def generate_id
20
146
  UUIDTools::UUID.random_create.to_s
21
147
  end
22
148
 
23
149
  public
150
+
24
151
  class << self
25
152
  def store_primary_key?
26
153
  true