massive_record 0.1.0

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