bigrecord 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
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