datamapper 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +2 -0
- data/MIT-LICENSE +22 -0
- data/README +1 -0
- data/example.rb +25 -0
- data/lib/data_mapper.rb +30 -0
- data/lib/data_mapper/adapters/abstract_adapter.rb +229 -0
- data/lib/data_mapper/adapters/mysql_adapter.rb +171 -0
- data/lib/data_mapper/adapters/sqlite3_adapter.rb +189 -0
- data/lib/data_mapper/associations.rb +19 -0
- data/lib/data_mapper/associations/belongs_to_association.rb +111 -0
- data/lib/data_mapper/associations/has_and_belongs_to_many_association.rb +100 -0
- data/lib/data_mapper/associations/has_many_association.rb +101 -0
- data/lib/data_mapper/associations/has_one_association.rb +107 -0
- data/lib/data_mapper/base.rb +160 -0
- data/lib/data_mapper/callbacks.rb +47 -0
- data/lib/data_mapper/database.rb +134 -0
- data/lib/data_mapper/extensions/active_record_impersonation.rb +69 -0
- data/lib/data_mapper/extensions/callback_helpers.rb +35 -0
- data/lib/data_mapper/identity_map.rb +21 -0
- data/lib/data_mapper/loaded_set.rb +45 -0
- data/lib/data_mapper/mappings/column.rb +78 -0
- data/lib/data_mapper/mappings/schema.rb +28 -0
- data/lib/data_mapper/mappings/table.rb +99 -0
- data/lib/data_mapper/queries/conditions.rb +141 -0
- data/lib/data_mapper/queries/connection.rb +34 -0
- data/lib/data_mapper/queries/create_table_statement.rb +38 -0
- data/lib/data_mapper/queries/delete_statement.rb +17 -0
- data/lib/data_mapper/queries/drop_table_statement.rb +17 -0
- data/lib/data_mapper/queries/insert_statement.rb +29 -0
- data/lib/data_mapper/queries/reader.rb +42 -0
- data/lib/data_mapper/queries/result.rb +19 -0
- data/lib/data_mapper/queries/select_statement.rb +103 -0
- data/lib/data_mapper/queries/table_exists_statement.rb +17 -0
- data/lib/data_mapper/queries/truncate_table_statement.rb +17 -0
- data/lib/data_mapper/queries/update_statement.rb +25 -0
- data/lib/data_mapper/session.rb +240 -0
- data/lib/data_mapper/support/blank_slate.rb +3 -0
- data/lib/data_mapper/support/connection_pool.rb +117 -0
- data/lib/data_mapper/support/enumerable.rb +27 -0
- data/lib/data_mapper/support/inflector.rb +329 -0
- data/lib/data_mapper/support/proc.rb +69 -0
- data/lib/data_mapper/support/string.rb +23 -0
- data/lib/data_mapper/support/symbol.rb +91 -0
- data/lib/data_mapper/support/weak_hash.rb +46 -0
- data/lib/data_mapper/unit_of_work.rb +38 -0
- data/lib/data_mapper/validations/confirmation_validator.rb +55 -0
- data/lib/data_mapper/validations/contextual_validations.rb +50 -0
- data/lib/data_mapper/validations/format_validator.rb +85 -0
- data/lib/data_mapper/validations/formats/email.rb +78 -0
- data/lib/data_mapper/validations/generic_validator.rb +27 -0
- data/lib/data_mapper/validations/length_validator.rb +75 -0
- data/lib/data_mapper/validations/required_field_validator.rb +47 -0
- data/lib/data_mapper/validations/unique_validator.rb +65 -0
- data/lib/data_mapper/validations/validation_errors.rb +34 -0
- data/lib/data_mapper/validations/validation_helper.rb +60 -0
- data/performance.rb +156 -0
- data/profile_data_mapper.rb +18 -0
- data/rakefile.rb +80 -0
- data/spec/basic_finder.rb +67 -0
- data/spec/belongs_to.rb +47 -0
- data/spec/fixtures/animals.yaml +32 -0
- data/spec/fixtures/exhibits.yaml +90 -0
- data/spec/fixtures/fruit.yaml +6 -0
- data/spec/fixtures/people.yaml +15 -0
- data/spec/fixtures/zoos.yaml +20 -0
- data/spec/has_and_belongs_to_many.rb +25 -0
- data/spec/has_many.rb +34 -0
- data/spec/legacy.rb +14 -0
- data/spec/models/animal.rb +7 -0
- data/spec/models/exhibit.rb +6 -0
- data/spec/models/fruit.rb +6 -0
- data/spec/models/person.rb +7 -0
- data/spec/models/post.rb +4 -0
- data/spec/models/sales_person.rb +4 -0
- data/spec/models/zoo.rb +5 -0
- data/spec/new_record.rb +24 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/sub_select.rb +16 -0
- data/spec/symbolic_operators.rb +21 -0
- data/spec/validates_confirmation_of.rb +36 -0
- data/spec/validates_format_of.rb +61 -0
- data/spec/validates_length_of.rb +101 -0
- data/spec/validates_uniqueness_of.rb +45 -0
- data/spec/validations.rb +63 -0
- metadata +134 -0
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'example'
|
2
|
+
require 'ruby-prof'
|
3
|
+
|
4
|
+
# RubyProf, making profiling Ruby pretty since 1899!
|
5
|
+
def profile(&b)
|
6
|
+
result = RubyProf.profile &b
|
7
|
+
|
8
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
9
|
+
File::open('profile_results.html', 'w+') do |file|
|
10
|
+
printer.print(file, 0)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
profile do
|
15
|
+
1000.times do
|
16
|
+
Zoo[:name => 'Galveston']
|
17
|
+
end
|
18
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
require 'rake/rdoctask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/contrib/rubyforgepublisher'
|
8
|
+
|
9
|
+
task :default => 'test'
|
10
|
+
|
11
|
+
desc "Run specifications"
|
12
|
+
Spec::Rake::SpecTask.new('test') do |t|
|
13
|
+
t.spec_opts = [ '-rspec/spec_helper' ]
|
14
|
+
t.spec_files = FileList[ENV['FILES'] || 'spec/*.rb']
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run comparison with ActiveRecord"
|
18
|
+
task :perf do
|
19
|
+
load 'performance.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "Profile DataMapper"
|
23
|
+
task :profile do
|
24
|
+
load 'profile_data_mapper.rb'
|
25
|
+
end
|
26
|
+
|
27
|
+
PACKAGE_VERSION = '0.1.0'
|
28
|
+
|
29
|
+
PACKAGE_FILES = FileList[
|
30
|
+
'README',
|
31
|
+
'CHANGELOG',
|
32
|
+
'MIT-LICENSE',
|
33
|
+
'*.rb',
|
34
|
+
'lib/**/*.rb',
|
35
|
+
'spec/**/*.{rb,yaml}'
|
36
|
+
].to_a
|
37
|
+
|
38
|
+
PROJECT = 'datamapper'
|
39
|
+
|
40
|
+
desc "Generate Documentation"
|
41
|
+
rd = Rake::RDocTask.new do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'doc'
|
43
|
+
rdoc.title = "DataMapper -- An Object/Relational Mapper for Ruby"
|
44
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
45
|
+
rdoc.rdoc_files.include(PACKAGE_FILES.reject { |path| path =~ /^(spec|\w+\.rb)/ })
|
46
|
+
end
|
47
|
+
|
48
|
+
gem_spec = Gem::Specification.new do |s|
|
49
|
+
s.platform = Gem::Platform::RUBY
|
50
|
+
s.name = PROJECT
|
51
|
+
s.summary = "An Object/Relational Mapper for Ruby"
|
52
|
+
s.description = "It's ActiveRecord, but Faster, Better, Simpler."
|
53
|
+
s.version = PACKAGE_VERSION
|
54
|
+
|
55
|
+
s.authors = 'Sam Smoot'
|
56
|
+
s.email = 'ssmoot@gmail.com'
|
57
|
+
s.rubyforge_project = PROJECT
|
58
|
+
s.homepage = 'http://datamapper.org'
|
59
|
+
|
60
|
+
s.files = PACKAGE_FILES
|
61
|
+
|
62
|
+
s.require_path = 'lib'
|
63
|
+
s.requirements << 'none'
|
64
|
+
s.autorequire = 'data_mapper'
|
65
|
+
|
66
|
+
s.has_rdoc = true
|
67
|
+
s.rdoc_options << '--line-numbers' << '--inline-source' << '--main' << 'README'
|
68
|
+
s.extra_rdoc_files = rd.rdoc_files.reject { |path| path =~ /\.rb$/ }.to_a
|
69
|
+
end
|
70
|
+
|
71
|
+
Rake::GemPackageTask.new(gem_spec) do |p|
|
72
|
+
p.gem_spec = gem_spec
|
73
|
+
p.need_tar = true
|
74
|
+
p.need_zip = true
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "Publish to RubyForge"
|
78
|
+
task :rubyforge => [ :rdoc, :gem ] do
|
79
|
+
Rake::SshDirPublisher.new(ENV['RUBYFORGE_USER'], "/var/www/gforge-projects/#{PROJECT}", 'doc').upload
|
80
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
context 'Finder' do
|
2
|
+
|
3
|
+
specify 'database-specific load should not fail' do
|
4
|
+
|
5
|
+
DataMapper::database do |db|
|
6
|
+
froggy = db.find(Animal, :first, :conditions => ['name = ?', 'Frog'])
|
7
|
+
froggy.name.should == 'Frog'
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
specify 'current-database load should not fail' do
|
13
|
+
froggy = DataMapper::database.find(Animal, :first).name.should == 'Frog'
|
14
|
+
end
|
15
|
+
|
16
|
+
specify 'load through ActiveRecord impersonation should not fail' do
|
17
|
+
Animal.find(:all).size.should == 16
|
18
|
+
end
|
19
|
+
|
20
|
+
specify 'load through Og impersonation should not fail' do
|
21
|
+
Animal.all.size.should == 16
|
22
|
+
end
|
23
|
+
|
24
|
+
specify ':conditions option should accept a hash' do
|
25
|
+
Animal.all(:conditions => { :name => 'Frog' }).size.should == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
specify 'non-standard options should be considered part of the conditions' do
|
29
|
+
database.log.debug('non-standard options should be considered part of the conditions')
|
30
|
+
zebra = Animal.first(:name => 'Zebra')
|
31
|
+
zebra.name.should == 'Zebra'
|
32
|
+
|
33
|
+
elephant = Animal[:name => 'Elephant']
|
34
|
+
elephant.name.should == 'Elephant'
|
35
|
+
|
36
|
+
aged = Person.all(:age => 29)
|
37
|
+
aged.size.should == 2
|
38
|
+
aged.first.name.should == 'Sam'
|
39
|
+
aged.last.name.should == 'Bob'
|
40
|
+
|
41
|
+
fixtures(:animals)
|
42
|
+
end
|
43
|
+
|
44
|
+
specify 'should not find deleted objects' do
|
45
|
+
database do
|
46
|
+
wally = Animal[:name => 'Whale']
|
47
|
+
wally.destroy!.should == true
|
48
|
+
|
49
|
+
wallys_evil_twin = Animal[:name => 'Whale']
|
50
|
+
wallys_evil_twin.should == nil
|
51
|
+
|
52
|
+
wally.new_record?.should == true
|
53
|
+
wally.save
|
54
|
+
|
55
|
+
Animal[:name => 'Whale'].should == wally
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
specify 'lazy-loads should issue for whole sets' do
|
60
|
+
people = Person.all
|
61
|
+
|
62
|
+
people.each do |person|
|
63
|
+
person.notes
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
data/spec/belongs_to.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
context 'An Exhibit' do
|
2
|
+
|
3
|
+
setup do
|
4
|
+
@aviary = Exhibit[:name => 'Monkey Mayhem']
|
5
|
+
end
|
6
|
+
|
7
|
+
specify 'has a zoo association' do
|
8
|
+
@aviary.zoo.class.should == Zoo
|
9
|
+
Exhibit.new.zoo.should == nil
|
10
|
+
end
|
11
|
+
|
12
|
+
specify 'belongs to a zoo' do
|
13
|
+
database do |db|
|
14
|
+
@aviary.zoo.should == @aviary.session.find(Zoo, :first, :name => 'San Diego')
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
specify 'can build its zoo' do
|
19
|
+
database do |db|
|
20
|
+
e = Exhibit.new({:name => 'Super Extra Crazy Monkey Cage'})
|
21
|
+
e.zoo.should == nil
|
22
|
+
e.build_zoo({:name => 'Monkey Zoo'})
|
23
|
+
e.zoo.class == Zoo
|
24
|
+
e.zoo.new_record?.should == true
|
25
|
+
|
26
|
+
# Need to get associations working properly before this works ....
|
27
|
+
e.save
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
specify 'can build its zoo' do
|
32
|
+
database do |db|
|
33
|
+
e = Exhibit.new({:name => 'Super Extra Crazy Monkey Cage'})
|
34
|
+
e.zoo.should == nil
|
35
|
+
e.create_zoo({:name => 'Monkey Zoo'})
|
36
|
+
e.zoo.class == Zoo
|
37
|
+
e.zoo.new_record?.should == false
|
38
|
+
e.save
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
teardown do
|
43
|
+
fixtures('zoos')
|
44
|
+
fixtures('exhibits')
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
- name: Frog
|
2
|
+
notes: I am a Frog!
|
3
|
+
- name: Bear
|
4
|
+
notes: I am a Bear!
|
5
|
+
- name: Giraffe
|
6
|
+
notes: I am a Giraffe!
|
7
|
+
- name: Zebra
|
8
|
+
notes: I am a Zebra!
|
9
|
+
- name: Cow
|
10
|
+
notes: I am a Cow!
|
11
|
+
- name: Pig
|
12
|
+
notes: I am a Pig!
|
13
|
+
- name: Cup
|
14
|
+
notes: I am a Cup!
|
15
|
+
- name: Cat
|
16
|
+
notes: I am a Cat!
|
17
|
+
- name: Dog
|
18
|
+
notes: I am a Dog!
|
19
|
+
- name: Elephant
|
20
|
+
notes: I am a Elephant!
|
21
|
+
- name: Whale
|
22
|
+
notes: I am a Whale!
|
23
|
+
- name: Dolphin
|
24
|
+
notes: I am a Dolphin!
|
25
|
+
- name: Mouse
|
26
|
+
notes: I am a Mouse!
|
27
|
+
- name: Rat
|
28
|
+
notes: I am a Rat!
|
29
|
+
- name: Camel
|
30
|
+
notes: I am a Camel!
|
31
|
+
- name: Fox
|
32
|
+
notes: I am a Fox!
|
@@ -0,0 +1,90 @@
|
|
1
|
+
- name: Horses & Courses
|
2
|
+
zoo_id: 1
|
3
|
+
- name: Aviary
|
4
|
+
zoo_id: 1
|
5
|
+
- name: Polar Planet
|
6
|
+
zoo_id: 2
|
7
|
+
- name: Monkey Mayhem
|
8
|
+
zoo_id: 2
|
9
|
+
- name: Amazonia
|
10
|
+
zoo_id: 3
|
11
|
+
- name: Deep Blue Ocean
|
12
|
+
zoo_id: 3
|
13
|
+
- name: Fish Fight!
|
14
|
+
zoo_id: 3
|
15
|
+
- name: The Dolphin Show
|
16
|
+
zoo_id: 3
|
17
|
+
- name: Canal Life
|
18
|
+
zoo_id: 4
|
19
|
+
- name: The Reptile Pit
|
20
|
+
zoo_id: 4
|
21
|
+
- name: Kangaroo Exhibit
|
22
|
+
zoo_id: 4
|
23
|
+
- name: A Million Years Ago...
|
24
|
+
zoo_id: 5
|
25
|
+
- name: The Wild West
|
26
|
+
zoo_id: 5
|
27
|
+
- name: Tumble Weeds
|
28
|
+
zoo_id: 5
|
29
|
+
- name: Missisipi Mud
|
30
|
+
zoo_id: 6
|
31
|
+
- name: Craw-Daddy's of the Deep!
|
32
|
+
zoo_id: 6
|
33
|
+
- name: The Littlest Big City
|
34
|
+
zoo_id: 7
|
35
|
+
- name: Jellyfish On Parade
|
36
|
+
zoo_id: 8
|
37
|
+
- name: Sea-Life Among the Rigs
|
38
|
+
zoo_id: 8
|
39
|
+
- name: Monsters of the Rainforest
|
40
|
+
zoo_id: 8
|
41
|
+
- name: So You Wanna Be A Street-Kid?
|
42
|
+
zoo_id: 9
|
43
|
+
- name: Don't Feed the Hippies!
|
44
|
+
zoo_id: 9
|
45
|
+
- name: At the Equator
|
46
|
+
zoo_id: 10
|
47
|
+
- name: River Habitats
|
48
|
+
zoo_id: 10
|
49
|
+
- name: A Fiesta of Plant-life
|
50
|
+
zoo_id: 10
|
51
|
+
- name: Smog World!
|
52
|
+
zoo_id: 11
|
53
|
+
- name: Where Are They Now? Extinct Species Edition
|
54
|
+
zoo_id: 11
|
55
|
+
- name: The Fragile Eco-System of the Cheese-Steak
|
56
|
+
zoo_id: 12
|
57
|
+
- name: Rats!
|
58
|
+
zoo_id: 13
|
59
|
+
- name: Penguins on Wall St.
|
60
|
+
zoo_id: 13
|
61
|
+
- name: King of the Jungle
|
62
|
+
zoo_id: 13
|
63
|
+
- name: The World's Fish-Market
|
64
|
+
zoo_id: 14
|
65
|
+
- name: Water-World
|
66
|
+
zoo_id: 14
|
67
|
+
- name: Micro-sized Soft-shelled Crabs
|
68
|
+
zoo_id: 14
|
69
|
+
- name: Do Animals Like Coffee?
|
70
|
+
zoo_id: 14
|
71
|
+
- name: On A Clear Day...
|
72
|
+
zoo_id: 15
|
73
|
+
- name: Twenty Thousand Leagues Under the Sky
|
74
|
+
zoo_id: 15
|
75
|
+
- name: Closed for Renovation
|
76
|
+
zoo_id: 16
|
77
|
+
- name: Ducks!
|
78
|
+
zoo_id: 17
|
79
|
+
- name: Retired Animal Life
|
80
|
+
zoo_id: 18
|
81
|
+
- name: The Clean Side of the Gulf
|
82
|
+
zoo_id: 18
|
83
|
+
- name: The Complete History of Rice
|
84
|
+
zoo_id: 19
|
85
|
+
- name: Safari World
|
86
|
+
zoo_id: 19
|
87
|
+
- name: Of Mice & Men
|
88
|
+
zoo_id: 20
|
89
|
+
- name: Snakes!
|
90
|
+
zoo_id: 20
|
@@ -0,0 +1,15 @@
|
|
1
|
+
- name: Sam
|
2
|
+
age: 29
|
3
|
+
occupation: Programmer
|
4
|
+
- name: Amy
|
5
|
+
age: 28
|
6
|
+
occupation: Business Analyst Manager
|
7
|
+
- name: Scott
|
8
|
+
age: 25
|
9
|
+
occupation: Programmer
|
10
|
+
- name: Josh
|
11
|
+
age: 23
|
12
|
+
occupation: Supervisor
|
13
|
+
- name: Bob
|
14
|
+
age: 29
|
15
|
+
occupation: Peon
|
@@ -0,0 +1,20 @@
|
|
1
|
+
- name: Dallas
|
2
|
+
- name: San Diego
|
3
|
+
- name: Miami
|
4
|
+
- name: Ft. Lauderdale
|
5
|
+
- name: Ft. Worth
|
6
|
+
- name: New Orleans
|
7
|
+
- name: San Antonio
|
8
|
+
- name: Galveston
|
9
|
+
- name: Austin
|
10
|
+
- name: Brownsville
|
11
|
+
- name: Los Angeles
|
12
|
+
- name: Phillidelphia
|
13
|
+
- name: New York
|
14
|
+
- name: Seattle
|
15
|
+
- name: Houston
|
16
|
+
- name: Corpus Christi
|
17
|
+
- name: Sacramento
|
18
|
+
- name: Pensacola
|
19
|
+
- name: Santa Francisco
|
20
|
+
- name: Orlando
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# context 'An Exhibit' do
|
2
|
+
#
|
3
|
+
# setup do
|
4
|
+
# @amazonia = Exhibit[:name => 'Amazonia']
|
5
|
+
# end
|
6
|
+
#
|
7
|
+
# specify 'has an animals association' do
|
8
|
+
# [@amazonia, Exhibit.new].each do |exhibit|
|
9
|
+
# exhibit.animals.class.should == DataMapper::Associations::HasAndBelongsToManyAssociation
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# specify 'has many animals' do
|
14
|
+
# @amazonia.animals.size.should == 2
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# specify 'should load associations magically' do
|
18
|
+
# Exhibit.all.each do |exhibit|
|
19
|
+
# exhibit.animals.each do |animal|
|
20
|
+
# animal.exhibits.should.include?(exhibit)
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# end
|
data/spec/has_many.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
describe DataMapper::Associations::HasManyAssociation do
|
2
|
+
|
3
|
+
before(:all) do
|
4
|
+
fixtures(:zoos)
|
5
|
+
fixtures(:exhibits)
|
6
|
+
|
7
|
+
@san_diego = Zoo[:name => 'San Diego']
|
8
|
+
@dallas = Zoo[:name => 'Dallas']
|
9
|
+
@miami = Zoo[:name => 'Miami']
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should expose a proxy for the accessor' do
|
13
|
+
[@miami, Zoo.new].each do |z|
|
14
|
+
z.exhibits.class.should == DataMapper::Associations::HasManyAssociation
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should lazily-load the association when Enumerable methods are called' do
|
19
|
+
database do |db|
|
20
|
+
@san_diego.exhibits.size.should == 2
|
21
|
+
@san_diego.exhibits.should include(@san_diego.session.find(Exhibit, :first, :name => 'Monkey Mayhem'))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should eager-load associations for an entire set' do
|
26
|
+
zoos = Zoo.all
|
27
|
+
zoos.each do |zoo|
|
28
|
+
zoo.exhibits.each do |exhibit|
|
29
|
+
exhibit.zoo.should == zoo
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|