massive_record 0.1.0

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 (81) hide show
  1. data/.autotest +15 -0
  2. data/.gitignore +6 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/Gemfile.lock +38 -0
  6. data/Manifest +24 -0
  7. data/README.md +225 -0
  8. data/Rakefile +16 -0
  9. data/TODO.md +8 -0
  10. data/autotest/discover.rb +1 -0
  11. data/lib/massive_record.rb +18 -0
  12. data/lib/massive_record/exceptions.rb +11 -0
  13. data/lib/massive_record/orm/attribute_methods.rb +61 -0
  14. data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
  15. data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
  16. data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
  17. data/lib/massive_record/orm/base.rb +176 -0
  18. data/lib/massive_record/orm/callbacks.rb +52 -0
  19. data/lib/massive_record/orm/column.rb +18 -0
  20. data/lib/massive_record/orm/config.rb +47 -0
  21. data/lib/massive_record/orm/errors.rb +47 -0
  22. data/lib/massive_record/orm/finders.rb +125 -0
  23. data/lib/massive_record/orm/id_factory.rb +133 -0
  24. data/lib/massive_record/orm/persistence.rb +199 -0
  25. data/lib/massive_record/orm/schema.rb +4 -0
  26. data/lib/massive_record/orm/schema/column_families.rb +48 -0
  27. data/lib/massive_record/orm/schema/column_family.rb +102 -0
  28. data/lib/massive_record/orm/schema/column_interface.rb +91 -0
  29. data/lib/massive_record/orm/schema/common_interface.rb +48 -0
  30. data/lib/massive_record/orm/schema/field.rb +128 -0
  31. data/lib/massive_record/orm/schema/fields.rb +37 -0
  32. data/lib/massive_record/orm/schema/table_interface.rb +96 -0
  33. data/lib/massive_record/orm/table.rb +9 -0
  34. data/lib/massive_record/orm/validations.rb +52 -0
  35. data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
  36. data/lib/massive_record/thrift/hbase.rb +2307 -0
  37. data/lib/massive_record/thrift/hbase_constants.rb +14 -0
  38. data/lib/massive_record/thrift/hbase_types.rb +225 -0
  39. data/lib/massive_record/version.rb +3 -0
  40. data/lib/massive_record/wrapper/base.rb +28 -0
  41. data/lib/massive_record/wrapper/cell.rb +45 -0
  42. data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
  43. data/lib/massive_record/wrapper/column_family.rb +22 -0
  44. data/lib/massive_record/wrapper/connection.rb +71 -0
  45. data/lib/massive_record/wrapper/row.rb +170 -0
  46. data/lib/massive_record/wrapper/scanner.rb +50 -0
  47. data/lib/massive_record/wrapper/table.rb +148 -0
  48. data/lib/massive_record/wrapper/tables_collection.rb +13 -0
  49. data/massive_record.gemspec +28 -0
  50. data/spec/config.yml.example +4 -0
  51. data/spec/orm/cases/attribute_methods_spec.rb +47 -0
  52. data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
  53. data/spec/orm/cases/base_spec.rb +176 -0
  54. data/spec/orm/cases/callbacks_spec.rb +309 -0
  55. data/spec/orm/cases/column_spec.rb +49 -0
  56. data/spec/orm/cases/config_spec.rb +103 -0
  57. data/spec/orm/cases/dirty_spec.rb +129 -0
  58. data/spec/orm/cases/encoding_spec.rb +49 -0
  59. data/spec/orm/cases/finders_spec.rb +208 -0
  60. data/spec/orm/cases/hbase/connection_spec.rb +13 -0
  61. data/spec/orm/cases/i18n_spec.rb +32 -0
  62. data/spec/orm/cases/id_factory_spec.rb +75 -0
  63. data/spec/orm/cases/persistence_spec.rb +479 -0
  64. data/spec/orm/cases/table_spec.rb +81 -0
  65. data/spec/orm/cases/validation_spec.rb +92 -0
  66. data/spec/orm/models/address.rb +7 -0
  67. data/spec/orm/models/person.rb +15 -0
  68. data/spec/orm/models/test_class.rb +5 -0
  69. data/spec/orm/schema/column_families_spec.rb +186 -0
  70. data/spec/orm/schema/column_family_spec.rb +131 -0
  71. data/spec/orm/schema/column_interface_spec.rb +115 -0
  72. data/spec/orm/schema/field_spec.rb +196 -0
  73. data/spec/orm/schema/fields_spec.rb +126 -0
  74. data/spec/orm/schema/table_interface_spec.rb +171 -0
  75. data/spec/spec_helper.rb +15 -0
  76. data/spec/support/connection_helpers.rb +76 -0
  77. data/spec/support/mock_massive_record_connection.rb +80 -0
  78. data/spec/thrift/cases/encoding_spec.rb +48 -0
  79. data/spec/wrapper/cases/connection_spec.rb +53 -0
  80. data/spec/wrapper/cases/table_spec.rb +231 -0
  81. metadata +228 -0
@@ -0,0 +1,50 @@
1
+ module MassiveRecord
2
+ module Wrapper
3
+ class Scanner
4
+
5
+ attr_accessor :connection, :table_name, :column_family_names, :opened_scanner
6
+ attr_accessor :start_key, :created_at, :limit
7
+ attr_accessor :formatted_column_family_names, :column_family_names
8
+
9
+ def initialize(connection, table_name, column_family_names, opts = {})
10
+ @connection = connection
11
+ @table_name = table_name
12
+ @column_family_names = column_family_names.collect{|n| n.split(":").first}
13
+ @column_family_names = opts[:columns] unless opts[:columns].nil?
14
+ @formatted_column_family_names = column_family_names.collect{|n| "#{n.split(":").first}:"}
15
+ @start_key = opts[:start_key].to_s
16
+ @created_at = opts[:created_at].to_s
17
+ @limit = opts[:limit] || 10
18
+ end
19
+
20
+ def open
21
+ if created_at.empty?
22
+ self.opened_scanner = connection.scannerOpen(table_name, start_key, formatted_column_family_names)
23
+ else
24
+ self.opened_scanner = connection.scannerOpenTs(table_name, start_key, formatted_column_family_names, created_at)
25
+ end
26
+ end
27
+
28
+ def close
29
+ connection.scannerClose(opened_scanner)
30
+ end
31
+
32
+ def fetch_trows(opts = {})
33
+ connection.scannerGetList(opened_scanner, limit)
34
+ end
35
+
36
+ def fetch_rows(opts = {})
37
+ populate_rows(fetch_trows(opts))
38
+ end
39
+
40
+ def populate_rows(results)
41
+ results.collect{|result| populate_row(result)}
42
+ end
43
+
44
+ def populate_row(result)
45
+ Row.populate_from_trow_result(result, connection, table_name, column_family_names)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,148 @@
1
+ module MassiveRecord
2
+ module Wrapper
3
+ class Table
4
+
5
+ attr_accessor :connection, :name, :column_families
6
+
7
+ def initialize(connection, table_name)
8
+ @connection = connection
9
+ @name = table_name.to_s
10
+ init_column_families
11
+ end
12
+
13
+ def init_column_families
14
+ @column_families = ColumnFamiliesCollection.new
15
+ @column_families.table = self
16
+ end
17
+
18
+ def self.create(connection, table_name, column_families = [])
19
+ table = self.new(connection, table_name)
20
+ table.column_families = column_families
21
+ table.save
22
+ end
23
+
24
+ def save
25
+ begin
26
+ client.createTable(name, @column_families.collect{|cf| cf.descriptor}).nil?
27
+ rescue Apache::Hadoop::Hbase::Thrift::AlreadyExists => ex
28
+ "The table already exists."
29
+ rescue => ex
30
+ raise ex
31
+ end
32
+ end
33
+
34
+ def client
35
+ connection
36
+ end
37
+
38
+ def disable
39
+ client.disableTable(name).nil?
40
+ end
41
+
42
+ def destroy
43
+ disable
44
+ client.deleteTable(name).nil?
45
+ end
46
+
47
+ def create_column_families(column_family_names)
48
+ column_family_names.each{|name| @column_families.push(ColumnFamily.new(name))}
49
+ end
50
+
51
+ def fetch_column_families
52
+ @column_families.clear
53
+ client.getColumnDescriptors(name).each do |column_name, description|
54
+ @column_families.push(ColumnFamily.new(column_name.split(":").first))
55
+ end
56
+ @column_families
57
+ end
58
+
59
+ def column_family_names
60
+ @column_families.collect{|column_family| column_family.name.to_s}
61
+ end
62
+
63
+ def fetch_column_family_names
64
+ fetch_column_families
65
+ column_family_names
66
+ end
67
+
68
+ def column_names
69
+ first.column_names
70
+ end
71
+
72
+ def scanner(opts = {})
73
+ scanner = Scanner.new(connection, name, column_family_names, format_options_for_scanner(opts))
74
+
75
+ if block_given?
76
+ begin
77
+ scanner.open
78
+ yield scanner
79
+ ensure
80
+ scanner.close
81
+ end
82
+ else
83
+ scanner
84
+ end
85
+ end
86
+
87
+ def format_options_for_scanner(opts = {})
88
+ {
89
+ :start_key => opts[:start],
90
+ :created_at => opts[:created_at],
91
+ :columns => opts[:select], # list of column families to fetch from hbase
92
+ :limit => opts[:limit] || opts[:batch_size]
93
+ }
94
+ end
95
+
96
+ def all(opts = {})
97
+ scanner(opts) do |s|
98
+ s.fetch_rows(opts)
99
+ end
100
+ end
101
+
102
+ def first(opts = {})
103
+ all(opts.merge(:limit => 1)).first
104
+ end
105
+
106
+ def find(*args)
107
+ arg = args[0]
108
+ opts = args[1] || {}
109
+ arg.is_a?(Array) ? arg.collect{|id| first(opts.merge(:start => id))} : first(opts.merge(:start => arg))
110
+ end
111
+
112
+ def find_in_batches(opts = {})
113
+ results_limit = opts.delete(:limit)
114
+ results_found = 0
115
+
116
+ scanner(opts) do |s|
117
+ while (true) do
118
+ s.limit = results_limit - results_found if !results_limit.nil? && results_limit <= results_found + s.limit
119
+ rows = s.fetch_rows
120
+ if rows.empty?
121
+ break
122
+ else
123
+ results_found += rows.size
124
+ yield rows
125
+ end
126
+ end
127
+ end
128
+ end
129
+
130
+ def exists?
131
+ connection.tables.include?(name)
132
+ end
133
+
134
+ def regions
135
+ connection.getTableRegions(name).collect do |r|
136
+ {
137
+ :start_key => r.startKey,
138
+ :end_key => r.endKey,
139
+ :id => r.id,
140
+ :name => r.name,
141
+ :version => r.version
142
+ }
143
+ end
144
+ end
145
+
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,13 @@
1
+ module MassiveRecord
2
+ module Wrapper
3
+ class TablesCollection < Array
4
+
5
+ attr_accessor :connection
6
+
7
+ def load(table_name)
8
+ Table.new(connection, table_name)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "massive_record/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "massive_record"
7
+ s.version = MassiveRecord::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Companybook"]
10
+ s.email = %q{geeks@companybook.no}
11
+ s.homepage = %q{http://github.com/CompanyBook/massive_record}
12
+ s.summary = %q{HBase Ruby client API}
13
+ s.description = %q{HBase Ruby client API}
14
+ s.rubyforge_project = "massive_record"
15
+
16
+
17
+ s.add_dependency "thrift", ">= 0.5.0"
18
+ s.add_dependency "activesupport"
19
+ s.add_dependency "activemodel"
20
+
21
+ s.add_development_dependency "rspec", ">= 2.1.0"
22
+
23
+
24
+ s.files = `git ls-files`.split("\n")
25
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
26
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
27
+ s.require_paths = ["lib"]
28
+ end
@@ -0,0 +1,4 @@
1
+ # Please copy the content of this file to ./config.yml to be able to run all the tests
2
+ host: somewhere.compute.amazonaws.com
3
+ port: 9090
4
+ table: massive_record_people_test
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe "attribute methods" do
5
+ before do
6
+ @model = Person.new :id => 5, :name => "John", :age => 15
7
+ end
8
+
9
+ it "should define reader method" do
10
+ @model.name.should == "John"
11
+ end
12
+
13
+ it "should define writer method" do
14
+ @model.name = "Bar"
15
+ @model.name.should == "Bar"
16
+ end
17
+
18
+ it "should be possible to write attributes" do
19
+ @model.write_attribute :name, "baaaaar"
20
+ @model.name.should == "baaaaar"
21
+ end
22
+
23
+ it "should be possible to read attributes" do
24
+ @model.read_attribute(:name).should == "John"
25
+ end
26
+
27
+ it "should contains the id in the attributes getter" do
28
+ @model.attributes.should include("id")
29
+ end
30
+
31
+ describe "#attributes=" do
32
+ it "should simply return if incomming value is not a hash" do
33
+ @model.attributes = "FOO BAR"
34
+ @model.attributes.keys.should include("name")
35
+ end
36
+
37
+ it "should mass assign attributes" do
38
+ @model.attributes = {:name => "Foo", :age => 20}
39
+ @model.name.should == "Foo"
40
+ @model.age.should == 20
41
+ end
42
+
43
+ it "should raise an error if we encounter an unkown attribute" do
44
+ lambda { @model.attributes = {:unkown => "foo"} }.should raise_error MassiveRecord::ORM::UnkownAttributeError
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,54 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/person'
3
+
4
+ describe "auto setting of ids" do
5
+ include MockMassiveRecordConnection
6
+
7
+ before do
8
+ @person = Person.new :name => "thorbjorn", :age => 29
9
+ end
10
+
11
+ it "should return nil as default if no default_id is defined" do
12
+ @person.id.should be_nil
13
+ end
14
+
15
+ it "should have id based on whatever default_id defines" do
16
+ Person.class_eval do
17
+ def default_id
18
+ [name, age].join("-")
19
+ end
20
+ end
21
+
22
+ @person.id.should == "thorbjorn-29"
23
+
24
+ Person.class_eval { undef_method :default_id }
25
+ end
26
+
27
+ it "should have id based on whatever default_id defines, even if it is private method" do
28
+ Person.class_eval do
29
+ private
30
+ def default_id
31
+ [name, age].join("-")
32
+ end
33
+ end
34
+
35
+ @person.id.should == "thorbjorn-29"
36
+
37
+ Person.class_eval { undef_method :default_id }
38
+ end
39
+
40
+ describe "#next_id" do
41
+ it "should ask IdFactory for a next id for self" do
42
+ Person.class_eval do
43
+ def default_id
44
+ next_id
45
+ end
46
+ end
47
+
48
+ MassiveRecord::ORM::IdFactory.should_receive(:next_for).with(Person).and_return(1)
49
+ @person.id.should == "1"
50
+
51
+ Person.class_eval { undef_method :default_id }
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+ require 'orm/models/test_class'
3
+
4
+ describe MassiveRecord::ORM::Base do
5
+ include MockMassiveRecordConnection
6
+
7
+ describe "table name" do
8
+ before do
9
+ TestClass.reset_table_name_configuration!
10
+ end
11
+
12
+ it "should have a table name" do
13
+ TestClass.table_name.should == "test_classes"
14
+ end
15
+
16
+ it "should have a table name with prefix" do
17
+ TestClass.table_name_prefix = "prefix_"
18
+ TestClass.table_name.should == "prefix_test_classes"
19
+ end
20
+
21
+ it "should have a table name with suffix" do
22
+ TestClass.table_name_suffix = "_suffix"
23
+ TestClass.table_name.should == "test_classes_suffix"
24
+ end
25
+
26
+ describe "set explicitly" do
27
+ it "should be able to set it" do
28
+ TestClass.table_name = "foo"
29
+ TestClass.table_name.should == "foo"
30
+ end
31
+
32
+ it "should have a table name with prefix" do
33
+ TestClass.table_name = "foo"
34
+ TestClass.table_name_prefix = "prefix_"
35
+ TestClass.table_name.should == "prefix_foo"
36
+ end
37
+
38
+ it "should have a table name with suffix" do
39
+ TestClass.table_name = "foo"
40
+ TestClass.table_name_suffix = "_suffix"
41
+ TestClass.table_name.should == "foo_suffix"
42
+ end
43
+
44
+ it "should be possible to call set_table_name" do
45
+ TestClass.set_table_name("foo")
46
+ TestClass.table_name.should == "foo"
47
+ end
48
+ end
49
+ end
50
+
51
+ it "should have a model name" do
52
+ TestClass.model_name.should == "TestClass"
53
+ end
54
+
55
+ describe "#initialize" do
56
+ it "should take a set of attributes and make them readable" do
57
+ model = TestClass.new :foo => :bar
58
+ model.foo.should == :bar
59
+ end
60
+
61
+ it "should initialize an object via init_with()" do
62
+ model = TestClass.allocate
63
+ model.init_with 'attributes' => {:foo => :bar}
64
+ model.foo.should == :bar
65
+ end
66
+
67
+ it "should stringify keys set on attributes" do
68
+ model = TestClass.allocate
69
+ model.init_with 'attributes' => {:foo => :bar}
70
+ model.attributes.keys.should include("foo")
71
+ end
72
+
73
+ it "should return nil as id by default" do
74
+ TestClass.new.id.should be_nil
75
+ end
76
+ end
77
+
78
+ describe "equality" do
79
+ it "should evaluate one object the same as equal" do
80
+ person = Person.find(1)
81
+ person.should == person
82
+ end
83
+
84
+ it "should evaluate two objects of same class and id as ==" do
85
+ Person.find(1).should == Person.find(1)
86
+ end
87
+
88
+ it "should evaluate two objects of same class and id as eql?" do
89
+ Person.find(1).eql?(Person.find(1)).should be_true
90
+ end
91
+
92
+ it "should not be equal if ids are different" do
93
+ Person.find(1).should_not == Person.find(2)
94
+ end
95
+
96
+ it "should not be equal if class are different" do
97
+ TestClass.find(1).should_not == Person.find(2)
98
+ end
99
+ end
100
+
101
+ describe "#to_param" do
102
+ it "should return nil if new record" do
103
+ TestClass.new.to_param.should be_nil
104
+ end
105
+
106
+ it "should return the id if persisted" do
107
+ TestClass.create!(:id => 1).to_param.should == "1"
108
+ end
109
+ end
110
+
111
+ describe "#to_key" do
112
+ it "should return nil if new record" do
113
+ TestClass.new.to_key.should be_nil
114
+ end
115
+
116
+ it "should return id in an array persisted" do
117
+ TestClass.create!(:id => "1").to_key.should == ["1"]
118
+ end
119
+ end
120
+
121
+ it "should be able to freeze objects" do
122
+ test_object = TestClass.new
123
+ test_object.freeze
124
+ test_object.should be_frozen
125
+ end
126
+
127
+
128
+ describe "#inspect" do
129
+ before do
130
+ @person = Person.new({
131
+ :name => "Bob",
132
+ :age => 3,
133
+ :date_of_birth => Date.today
134
+ })
135
+ end
136
+
137
+ it "should wrap inspection string inside of #< >" do
138
+ @person.inspect.should match(/^#<.*?>$/);
139
+ end
140
+
141
+ it "should contain it's class name" do
142
+ @person.inspect.should include("Person")
143
+ end
144
+
145
+ it "should start with the record's id if it has any" do
146
+ @person.id = 3
147
+ @person.inspect.should include "#<Person id: 3,"
148
+ end
149
+
150
+ it "should start with the record's id if it has any" do
151
+ @person.id = nil
152
+ @person.inspect.should include "#<Person id: nil,"
153
+ end
154
+
155
+ it "should contain a nice list of it's attributes" do
156
+ i = @person.inspect
157
+ i.should include(%q{name: "Bob"})
158
+ i.should include(%q{age: 3})
159
+ end
160
+ end
161
+
162
+ describe "attribute read / write alias" do
163
+ before do
164
+ @test_object = TestClass.new :foo => 'bar'
165
+ end
166
+
167
+ it "should read attributes by object[attr]" do
168
+ @test_object[:foo].should == 'bar'
169
+ end
170
+
171
+ it "should write attributes by object[attr] = new_value" do
172
+ @test_object["foo"] = "new_value"
173
+ @test_object.foo.should == "new_value"
174
+ end
175
+ end
176
+ end