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.
- data/.autotest +15 -0
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +38 -0
- data/Manifest +24 -0
- data/README.md +225 -0
- data/Rakefile +16 -0
- data/TODO.md +8 -0
- data/autotest/discover.rb +1 -0
- data/lib/massive_record.rb +18 -0
- data/lib/massive_record/exceptions.rb +11 -0
- data/lib/massive_record/orm/attribute_methods.rb +61 -0
- data/lib/massive_record/orm/attribute_methods/dirty.rb +80 -0
- data/lib/massive_record/orm/attribute_methods/read.rb +23 -0
- data/lib/massive_record/orm/attribute_methods/write.rb +24 -0
- data/lib/massive_record/orm/base.rb +176 -0
- data/lib/massive_record/orm/callbacks.rb +52 -0
- data/lib/massive_record/orm/column.rb +18 -0
- data/lib/massive_record/orm/config.rb +47 -0
- data/lib/massive_record/orm/errors.rb +47 -0
- data/lib/massive_record/orm/finders.rb +125 -0
- data/lib/massive_record/orm/id_factory.rb +133 -0
- data/lib/massive_record/orm/persistence.rb +199 -0
- data/lib/massive_record/orm/schema.rb +4 -0
- data/lib/massive_record/orm/schema/column_families.rb +48 -0
- data/lib/massive_record/orm/schema/column_family.rb +102 -0
- data/lib/massive_record/orm/schema/column_interface.rb +91 -0
- data/lib/massive_record/orm/schema/common_interface.rb +48 -0
- data/lib/massive_record/orm/schema/field.rb +128 -0
- data/lib/massive_record/orm/schema/fields.rb +37 -0
- data/lib/massive_record/orm/schema/table_interface.rb +96 -0
- data/lib/massive_record/orm/table.rb +9 -0
- data/lib/massive_record/orm/validations.rb +52 -0
- data/lib/massive_record/spec/support/simple_database_cleaner.rb +52 -0
- data/lib/massive_record/thrift/hbase.rb +2307 -0
- data/lib/massive_record/thrift/hbase_constants.rb +14 -0
- data/lib/massive_record/thrift/hbase_types.rb +225 -0
- data/lib/massive_record/version.rb +3 -0
- data/lib/massive_record/wrapper/base.rb +28 -0
- data/lib/massive_record/wrapper/cell.rb +45 -0
- data/lib/massive_record/wrapper/column_families_collection.rb +19 -0
- data/lib/massive_record/wrapper/column_family.rb +22 -0
- data/lib/massive_record/wrapper/connection.rb +71 -0
- data/lib/massive_record/wrapper/row.rb +170 -0
- data/lib/massive_record/wrapper/scanner.rb +50 -0
- data/lib/massive_record/wrapper/table.rb +148 -0
- data/lib/massive_record/wrapper/tables_collection.rb +13 -0
- data/massive_record.gemspec +28 -0
- data/spec/config.yml.example +4 -0
- data/spec/orm/cases/attribute_methods_spec.rb +47 -0
- data/spec/orm/cases/auto_generate_id_spec.rb +54 -0
- data/spec/orm/cases/base_spec.rb +176 -0
- data/spec/orm/cases/callbacks_spec.rb +309 -0
- data/spec/orm/cases/column_spec.rb +49 -0
- data/spec/orm/cases/config_spec.rb +103 -0
- data/spec/orm/cases/dirty_spec.rb +129 -0
- data/spec/orm/cases/encoding_spec.rb +49 -0
- data/spec/orm/cases/finders_spec.rb +208 -0
- data/spec/orm/cases/hbase/connection_spec.rb +13 -0
- data/spec/orm/cases/i18n_spec.rb +32 -0
- data/spec/orm/cases/id_factory_spec.rb +75 -0
- data/spec/orm/cases/persistence_spec.rb +479 -0
- data/spec/orm/cases/table_spec.rb +81 -0
- data/spec/orm/cases/validation_spec.rb +92 -0
- data/spec/orm/models/address.rb +7 -0
- data/spec/orm/models/person.rb +15 -0
- data/spec/orm/models/test_class.rb +5 -0
- data/spec/orm/schema/column_families_spec.rb +186 -0
- data/spec/orm/schema/column_family_spec.rb +131 -0
- data/spec/orm/schema/column_interface_spec.rb +115 -0
- data/spec/orm/schema/field_spec.rb +196 -0
- data/spec/orm/schema/fields_spec.rb +126 -0
- data/spec/orm/schema/table_interface_spec.rb +171 -0
- data/spec/spec_helper.rb +15 -0
- data/spec/support/connection_helpers.rb +76 -0
- data/spec/support/mock_massive_record_connection.rb +80 -0
- data/spec/thrift/cases/encoding_spec.rb +48 -0
- data/spec/wrapper/cases/connection_spec.rb +53 -0
- data/spec/wrapper/cases/table_spec.rb +231 -0
- 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,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,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
|