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