datamapper-dm-core 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (131) hide show
  1. data/.autotest +26 -0
  2. data/.gitignore +18 -0
  3. data/CONTRIBUTING +51 -0
  4. data/FAQ +92 -0
  5. data/History.txt +41 -0
  6. data/MIT-LICENSE +22 -0
  7. data/Manifest.txt +130 -0
  8. data/QUICKLINKS +11 -0
  9. data/README.txt +143 -0
  10. data/Rakefile +30 -0
  11. data/SPECS +62 -0
  12. data/TODO +1 -0
  13. data/dm-core.gemspec +40 -0
  14. data/lib/dm-core.rb +217 -0
  15. data/lib/dm-core/adapters.rb +16 -0
  16. data/lib/dm-core/adapters/abstract_adapter.rb +209 -0
  17. data/lib/dm-core/adapters/data_objects_adapter.rb +716 -0
  18. data/lib/dm-core/adapters/in_memory_adapter.rb +87 -0
  19. data/lib/dm-core/adapters/mysql_adapter.rb +136 -0
  20. data/lib/dm-core/adapters/postgres_adapter.rb +189 -0
  21. data/lib/dm-core/adapters/sqlite3_adapter.rb +105 -0
  22. data/lib/dm-core/associations.rb +207 -0
  23. data/lib/dm-core/associations/many_to_many.rb +147 -0
  24. data/lib/dm-core/associations/many_to_one.rb +107 -0
  25. data/lib/dm-core/associations/one_to_many.rb +315 -0
  26. data/lib/dm-core/associations/one_to_one.rb +61 -0
  27. data/lib/dm-core/associations/relationship.rb +229 -0
  28. data/lib/dm-core/associations/relationship_chain.rb +81 -0
  29. data/lib/dm-core/auto_migrations.rb +105 -0
  30. data/lib/dm-core/collection.rb +670 -0
  31. data/lib/dm-core/dependency_queue.rb +32 -0
  32. data/lib/dm-core/hook.rb +11 -0
  33. data/lib/dm-core/identity_map.rb +42 -0
  34. data/lib/dm-core/is.rb +16 -0
  35. data/lib/dm-core/logger.rb +232 -0
  36. data/lib/dm-core/migrations/destructive_migrations.rb +17 -0
  37. data/lib/dm-core/migrator.rb +29 -0
  38. data/lib/dm-core/model.rb +526 -0
  39. data/lib/dm-core/naming_conventions.rb +84 -0
  40. data/lib/dm-core/property.rb +676 -0
  41. data/lib/dm-core/property_set.rb +169 -0
  42. data/lib/dm-core/query.rb +676 -0
  43. data/lib/dm-core/repository.rb +167 -0
  44. data/lib/dm-core/resource.rb +671 -0
  45. data/lib/dm-core/scope.rb +58 -0
  46. data/lib/dm-core/support.rb +7 -0
  47. data/lib/dm-core/support/array.rb +13 -0
  48. data/lib/dm-core/support/assertions.rb +8 -0
  49. data/lib/dm-core/support/errors.rb +23 -0
  50. data/lib/dm-core/support/kernel.rb +11 -0
  51. data/lib/dm-core/support/symbol.rb +41 -0
  52. data/lib/dm-core/transaction.rb +267 -0
  53. data/lib/dm-core/type.rb +160 -0
  54. data/lib/dm-core/type_map.rb +80 -0
  55. data/lib/dm-core/types.rb +19 -0
  56. data/lib/dm-core/types/boolean.rb +7 -0
  57. data/lib/dm-core/types/discriminator.rb +34 -0
  58. data/lib/dm-core/types/object.rb +24 -0
  59. data/lib/dm-core/types/paranoid_boolean.rb +34 -0
  60. data/lib/dm-core/types/paranoid_datetime.rb +33 -0
  61. data/lib/dm-core/types/serial.rb +9 -0
  62. data/lib/dm-core/types/text.rb +10 -0
  63. data/lib/dm-core/version.rb +3 -0
  64. data/script/all +4 -0
  65. data/script/performance.rb +282 -0
  66. data/script/profile.rb +87 -0
  67. data/spec/integration/association_spec.rb +1382 -0
  68. data/spec/integration/association_through_spec.rb +203 -0
  69. data/spec/integration/associations/many_to_many_spec.rb +449 -0
  70. data/spec/integration/associations/many_to_one_spec.rb +163 -0
  71. data/spec/integration/associations/one_to_many_spec.rb +188 -0
  72. data/spec/integration/auto_migrations_spec.rb +413 -0
  73. data/spec/integration/collection_spec.rb +1073 -0
  74. data/spec/integration/data_objects_adapter_spec.rb +32 -0
  75. data/spec/integration/dependency_queue_spec.rb +46 -0
  76. data/spec/integration/model_spec.rb +197 -0
  77. data/spec/integration/mysql_adapter_spec.rb +85 -0
  78. data/spec/integration/postgres_adapter_spec.rb +731 -0
  79. data/spec/integration/property_spec.rb +253 -0
  80. data/spec/integration/query_spec.rb +514 -0
  81. data/spec/integration/repository_spec.rb +61 -0
  82. data/spec/integration/resource_spec.rb +513 -0
  83. data/spec/integration/sqlite3_adapter_spec.rb +352 -0
  84. data/spec/integration/sti_spec.rb +273 -0
  85. data/spec/integration/strategic_eager_loading_spec.rb +156 -0
  86. data/spec/integration/transaction_spec.rb +75 -0
  87. data/spec/integration/type_spec.rb +275 -0
  88. data/spec/lib/logging_helper.rb +18 -0
  89. data/spec/lib/mock_adapter.rb +27 -0
  90. data/spec/lib/model_loader.rb +100 -0
  91. data/spec/lib/publicize_methods.rb +28 -0
  92. data/spec/models/content.rb +16 -0
  93. data/spec/models/vehicles.rb +34 -0
  94. data/spec/models/zoo.rb +48 -0
  95. data/spec/spec.opts +3 -0
  96. data/spec/spec_helper.rb +91 -0
  97. data/spec/unit/adapters/abstract_adapter_spec.rb +133 -0
  98. data/spec/unit/adapters/adapter_shared_spec.rb +15 -0
  99. data/spec/unit/adapters/data_objects_adapter_spec.rb +632 -0
  100. data/spec/unit/adapters/in_memory_adapter_spec.rb +98 -0
  101. data/spec/unit/adapters/postgres_adapter_spec.rb +133 -0
  102. data/spec/unit/associations/many_to_many_spec.rb +32 -0
  103. data/spec/unit/associations/many_to_one_spec.rb +159 -0
  104. data/spec/unit/associations/one_to_many_spec.rb +393 -0
  105. data/spec/unit/associations/one_to_one_spec.rb +7 -0
  106. data/spec/unit/associations/relationship_spec.rb +71 -0
  107. data/spec/unit/associations_spec.rb +242 -0
  108. data/spec/unit/auto_migrations_spec.rb +111 -0
  109. data/spec/unit/collection_spec.rb +182 -0
  110. data/spec/unit/data_mapper_spec.rb +35 -0
  111. data/spec/unit/identity_map_spec.rb +126 -0
  112. data/spec/unit/is_spec.rb +80 -0
  113. data/spec/unit/migrator_spec.rb +33 -0
  114. data/spec/unit/model_spec.rb +321 -0
  115. data/spec/unit/naming_conventions_spec.rb +36 -0
  116. data/spec/unit/property_set_spec.rb +90 -0
  117. data/spec/unit/property_spec.rb +753 -0
  118. data/spec/unit/query_spec.rb +571 -0
  119. data/spec/unit/repository_spec.rb +93 -0
  120. data/spec/unit/resource_spec.rb +649 -0
  121. data/spec/unit/scope_spec.rb +142 -0
  122. data/spec/unit/transaction_spec.rb +493 -0
  123. data/spec/unit/type_map_spec.rb +114 -0
  124. data/spec/unit/type_spec.rb +119 -0
  125. data/tasks/ci.rb +36 -0
  126. data/tasks/dm.rb +63 -0
  127. data/tasks/doc.rb +20 -0
  128. data/tasks/gemspec.rb +23 -0
  129. data/tasks/hoe.rb +46 -0
  130. data/tasks/install.rb +20 -0
  131. metadata +215 -0
@@ -0,0 +1,114 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ describe DataMapper::TypeMap do
5
+
6
+ before(:each) do
7
+ @tm = DataMapper::TypeMap.new
8
+ end
9
+
10
+ describe "#map" do
11
+ it "should return a type map chain" do
12
+ @tm.map(String).should be_instance_of(DataMapper::TypeMap::TypeChain)
13
+ end
14
+
15
+ it "should return the original chain if the type has already been mapped" do
16
+ tc = @tm.map(String)
17
+ @tm.map(String).should == tc
18
+ end
19
+ end
20
+
21
+ describe "#lookup" do
22
+ it "should the primitive's mapping the class has a primitive type" do
23
+ @tm.map(String).to(:varchar)
24
+
25
+ lambda { @tm.lookup(DM::Text) }.should_not raise_error
26
+ end
27
+
28
+ it "should merge in the parent type map's translated match" do
29
+
30
+ end
31
+
32
+ describe "#lookup_from_map" do
33
+ it "should merge the translated type match into the parent match" do
34
+ @tm.map(String).to(:varchar)
35
+
36
+ child = DataMapper::TypeMap.new(@tm)
37
+ child.map(String).with(:size => 100)
38
+
39
+ child.lookup_from_map(String).should == {:primitive => :varchar, :size => 100}
40
+ end
41
+ end
42
+
43
+ describe "#lookup_by_type" do
44
+ it "should raise an exception if the type is not mapped and does not have a primitive" do
45
+ klass = Class.new
46
+ lambda { @tm.lookup(klass) }.should raise_error("Type #{klass} must have a default primitive or type map entry")
47
+ end
48
+ end
49
+ end
50
+
51
+ describe "#map" do
52
+ it "should create a new TypeChain if there is no match" do
53
+ @tm.chains.should_not have_key(String)
54
+
55
+ DataMapper::TypeMap::TypeChain.should_receive(:new)
56
+
57
+ @tm.map(String)
58
+ end
59
+
60
+ it "should not create a new TypeChain if there is a match" do
61
+ @tm.map(String)
62
+
63
+ DataMapper::TypeMap::TypeChain.should_not_receive(:new)
64
+
65
+ @tm.map(String)
66
+ end
67
+ end
68
+
69
+ describe DataMapper::TypeMap::TypeChain do
70
+ describe "#to" do
71
+ it "should be a setter for @primitive" do
72
+ tc = DataMapper::TypeMap::TypeChain.new
73
+
74
+ lambda { tc.to(:primitive) }.should change { tc.primitive }.to(:primitive)
75
+ end
76
+
77
+ it "should return itself" do
78
+ tc = DataMapper::TypeMap::TypeChain.new
79
+
80
+ tc.to(:primitive).should == tc
81
+ end
82
+ end
83
+
84
+ describe "#with" do
85
+ it "should return itself" do
86
+ tc = DataMapper::TypeMap::TypeChain.new
87
+
88
+ tc.with(:key => :value).should == tc
89
+ end
90
+
91
+ it "should raise an error if the argument is not a hash" do
92
+ tc = DataMapper::TypeMap::TypeChain.new
93
+
94
+ lambda { tc.with(:key) }.should raise_error("method 'with' expects a hash")
95
+ end
96
+
97
+ it "should merge the argument hash into the attributes hash" do
98
+ tc = DataMapper::TypeMap::TypeChain.new
99
+
100
+ tc.with(:key => :value).with(:size => 10).attributes.should == {:key => :value, :size => 10}
101
+ end
102
+ end
103
+
104
+ describe "#translate" do
105
+ it "should merge the attributes hash with the primitive value" do
106
+ DataMapper::TypeMap::TypeChain.new.to(:int).with(:size => 10).translate.should == {:primitive => :int, :size => 10}
107
+ end
108
+
109
+ it "should overwrite any :primitive entry set using the 'with' method" do
110
+ DataMapper::TypeMap::TypeChain.new.to(:int).with(:primitive => :varchar).translate.should == {:primitive => :int}
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,119 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'spec_helper'))
2
+
3
+ describe DataMapper::Type do
4
+
5
+ before(:each) do
6
+ class TestType < DataMapper::Type
7
+ primitive String
8
+ size 10
9
+ end
10
+
11
+ class TestType2 < DataMapper::Type
12
+ primitive String
13
+ size 10
14
+
15
+ def self.load(value, property)
16
+ value.reverse
17
+ end
18
+
19
+ def self.dump(value, property)
20
+ value.reverse
21
+ end
22
+ end
23
+
24
+ class TestResource
25
+ include DataMapper::Resource
26
+ end
27
+
28
+ class TestType3 < DataMapper::Type
29
+ primitive String
30
+ size 10
31
+ attr_accessor :property, :value
32
+
33
+ def self.load(value, property)
34
+ type = self.new
35
+ type.property = property
36
+ type.value = value
37
+ type
38
+ end
39
+
40
+ def self.dump(value, property)
41
+ value.value
42
+ end
43
+ end
44
+ end
45
+
46
+ it "should have the same PROPERTY_OPTIONS array as DataMapper::Property" do
47
+ DataMapper::Type::PROPERTY_OPTIONS.should == DataMapper::Property::PROPERTY_OPTIONS
48
+ end
49
+
50
+ it "should create a new type based on String primitive" do
51
+ TestType.primitive.should == String
52
+ end
53
+
54
+ it "should have size of 10" do
55
+ TestType.size.should == 10
56
+ end
57
+
58
+ it "should have options hash exactly equal to options specified in custom type" do
59
+ #ie. it should not include null elements
60
+ TestType.options.should == { :size => 10, :length => 10 }
61
+ end
62
+
63
+ it "should have length aliased to size" do
64
+ TestType.length.should == TestType.size
65
+ end
66
+
67
+ it "should pass through the value if load wasn't overridden" do
68
+ TestType.load("test", nil).should == "test"
69
+ end
70
+
71
+ it "should pass through the value if dump wasn't overridden" do
72
+ TestType.dump("test", nil).should == "test"
73
+ end
74
+
75
+ it "should not raise NotImplementedException if load was overridden" do
76
+ TestType2.dump("helo", nil).should == "oleh"
77
+ end
78
+
79
+ it "should not raise NotImplementedException if dump was overridden" do
80
+ TestType2.load("oleh", nil).should == "helo"
81
+ end
82
+
83
+ describe "using a custom type" do
84
+ before do
85
+ @property = DataMapper::Property.new TestResource, :name, TestType3, {}
86
+ end
87
+
88
+ it "should return a object of the same type" do
89
+ TestType3.load("helo", @property).class.should == TestType3
90
+ end
91
+
92
+ it "should contain the property" do
93
+ TestType3.load("helo", @property).property.should == @property
94
+ end
95
+
96
+ it "should contain the value" do
97
+ TestType3.load("helo", @property).value.should == "helo"
98
+ end
99
+
100
+ it "should return the value" do
101
+ obj = TestType3.load("helo", @property)
102
+ TestType3.dump(obj, @property).should == "helo"
103
+ end
104
+ end
105
+
106
+ describe "using def Type" do
107
+ before do
108
+ @class = Class.new(DataMapper::Type(String, :size => 20))
109
+ end
110
+
111
+ it "should be of the specified type" do
112
+ @class.primitive.should == String
113
+ end
114
+
115
+ it "should have the right options set" do
116
+ @class.size.should == 20
117
+ end
118
+ end
119
+ end
data/tasks/ci.rb ADDED
@@ -0,0 +1,36 @@
1
+ task 'ci:doc' => :doc
2
+
3
+ namespace :ci do
4
+
5
+ task :prepare do
6
+ rm_rf ROOT + "ci"
7
+ mkdir_p ROOT + "ci"
8
+ mkdir_p ROOT + "ci/doc"
9
+ mkdir_p ROOT + "ci/cyclomatic"
10
+ mkdir_p ROOT + "ci/token"
11
+ end
12
+
13
+ Spec::Rake::SpecTask.new(:spec => :prepare) do |t|
14
+ t.spec_opts = ["--colour", "--format", "specdoc", "--format", "html:#{ROOT}/ci/rspec_report.html", "--diff"]
15
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
16
+ unless ENV['NO_RCOV']
17
+ t.rcov = true
18
+ t.rcov_opts << '--exclude' << "spec,gems"
19
+ t.rcov_opts << '--text-summary'
20
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
21
+ t.rcov_opts << '--only-uncovered'
22
+ end
23
+ end
24
+
25
+ task :saikuro => :prepare do
26
+ system "saikuro -c -i lib -y 0 -w 10 -e 15 -o ci/cyclomatic"
27
+ mv 'ci/cyclomatic/index_cyclo.html', 'ci/cyclomatic/index.html'
28
+
29
+ system "saikuro -t -i lib -y 0 -w 20 -e 30 -o ci/token"
30
+ mv 'ci/token/index_token.html', 'ci/token/index.html'
31
+ end
32
+
33
+ end
34
+
35
+ #task :ci => %w[ ci:spec ci:doc ci:saikuro install ci:publish ] # yard-related tasks do not work yet
36
+ task :ci => %w[ ci:spec ]
data/tasks/dm.rb ADDED
@@ -0,0 +1,63 @@
1
+ task :default => 'dm:spec'
2
+ task :spec => 'dm:spec'
3
+ task :rcov => 'dm:rcov'
4
+
5
+ namespace :spec do
6
+ task :unit => 'dm:spec:unit'
7
+ task :integration => 'dm:spec:integration'
8
+ end
9
+
10
+ namespace :rcov do
11
+ task :unit => 'dm:rcov:unit'
12
+ task :integration => 'dm:rcov:integration'
13
+ end
14
+
15
+ namespace :dm do
16
+ def run_spec(name, files, rcov)
17
+ Spec::Rake::SpecTask.new(name) do |t|
18
+ t.spec_opts << '--colour' << '--loadby' << 'random'
19
+ t.spec_files = Pathname.glob(ENV['FILES'] || files.to_s).map { |f| f.to_s }
20
+ t.rcov = rcov
21
+ t.rcov_opts << '--exclude' << 'spec,environment.rb'
22
+ t.rcov_opts << '--text-summary'
23
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
24
+ t.rcov_opts << '--only-uncovered'
25
+ end
26
+ end
27
+
28
+ unit_specs = ROOT + 'spec/unit/**/*_spec.rb'
29
+ integration_specs = ROOT + 'spec/integration/**/*_spec.rb'
30
+ all_specs = ROOT + 'spec/**/*_spec.rb'
31
+
32
+ desc "Run all specifications"
33
+ run_spec('spec', all_specs, false)
34
+
35
+ desc "Run all specifications with rcov"
36
+ run_spec('rcov', all_specs, true)
37
+
38
+ namespace :spec do
39
+ desc "Run unit specifications"
40
+ run_spec('unit', unit_specs, false)
41
+
42
+ desc "Run integration specifications"
43
+ run_spec('integration', integration_specs, false)
44
+ end
45
+
46
+ namespace :rcov do
47
+ desc "Run unit specifications with rcov"
48
+ run_spec('unit', unit_specs, true)
49
+
50
+ desc "Run integration specifications with rcov"
51
+ run_spec('integration', integration_specs, true)
52
+ end
53
+
54
+ desc "Run all comparisons with ActiveRecord"
55
+ task :perf do
56
+ sh ROOT + 'script/performance.rb'
57
+ end
58
+
59
+ desc "Profile DataMapper"
60
+ task :profile do
61
+ sh ROOT + 'script/profile.rb'
62
+ end
63
+ end
data/tasks/doc.rb ADDED
@@ -0,0 +1,20 @@
1
+ # when yard's ready, it'll have to come back, but for now...
2
+ Rake::RDocTask.new("doc") do |t|
3
+ t.rdoc_dir = 'doc'
4
+ t.title = "DataMapper - Ruby Object Relational Mapper"
5
+ t.options = ['--line-numbers', '--inline-source', '--all']
6
+ t.rdoc_files.include("README.txt", "QUICKLINKS", "FAQ", "lib/**/**/*.rb")
7
+ end
8
+
9
+ begin
10
+ gem 'yard', '>=0.2.1'
11
+ require 'yard'
12
+
13
+ YARD::Rake::YardocTask.new("yardoc") do |t|
14
+ t.options << '--protected'
15
+ # t.options << '-q'
16
+ # t.files << '...anyglobshere...'
17
+ end
18
+ rescue Exception
19
+ # yard not installed
20
+ end
data/tasks/gemspec.rb ADDED
@@ -0,0 +1,23 @@
1
+ desc "Generate gemspec"
2
+ task :gemspec do |x|
3
+ # Clean up extraneous files before checking manifest
4
+ %x[rake clean]
5
+
6
+ # Check the manifest before generating the gemspec
7
+ manifest = %x[rake check_manifest]
8
+ manifest.gsub!("(in /usr/local/projects/dm/dm-core)\n", "")
9
+
10
+ unless manifest.empty?
11
+ print "\n", "#"*68, "\n"
12
+ print <<-EOS
13
+ Manifest.txt is not up-to-date. Please review the changes below.
14
+ If the changes are correct, run 'rake check_manifest | patch'
15
+ and then run this command again.
16
+ EOS
17
+ print "#"*68, "\n\n"
18
+ puts manifest
19
+ else
20
+ %x[rake debug_gem > #{GEM_NAME}.gemspec]
21
+ puts "Successfully created gemspec for #{GEM_NAME}!"
22
+ end
23
+ end
data/tasks/hoe.rb ADDED
@@ -0,0 +1,46 @@
1
+ require 'hoe'
2
+
3
+ @config_file = "~/.rubyforge/user-config.yml"
4
+ @config = nil
5
+ RUBYFORGE_USERNAME = "unknown"
6
+ def rubyforge_username
7
+ unless @config
8
+ begin
9
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
10
+ rescue
11
+ puts <<-EOS
12
+ ERROR: No rubyforge config file found: #{@config_file}
13
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
14
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
15
+ EOS
16
+ exit
17
+ end
18
+ end
19
+ RUBYFORGE_USERNAME.replace @config["username"]
20
+ end
21
+
22
+ # Remove hoe dependency
23
+ class Hoe
24
+ def extra_dev_deps
25
+ @extra_dev_deps.reject! { |dep| dep[0] == "hoe" }
26
+ @extra_dev_deps
27
+ end
28
+ end
29
+
30
+ hoe = Hoe.new(GEM_NAME, GEM_VERSION) do |p|
31
+
32
+ p.developer(AUTHOR, EMAIL)
33
+
34
+ p.description = PROJECT_DESCRIPTION
35
+ p.summary = PROJECT_SUMMARY
36
+ p.url = PROJECT_URL
37
+
38
+ p.rubyforge_name = PROJECT_NAME if PROJECT_NAME
39
+
40
+ p.clean_globs |= ["{coverage,doc,log,tmp}", "**/*.{log,db}", "profile_results.*", "**/.DS_Store"]
41
+
42
+ GEM_DEPENDENCIES.each do |dep|
43
+ p.extra_deps << dep
44
+ end
45
+
46
+ end
data/tasks/install.rb ADDED
@@ -0,0 +1,20 @@
1
+ WIN32 = (RUBY_PLATFORM =~ /win32|mingw|bccwin|cygwin/) rescue nil
2
+ SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
3
+
4
+ desc "Install #{GEM_NAME}"
5
+ if WIN32
6
+ task :install => :gem do
7
+ system %{gem install --no-rdoc --no-ri -l pkg/#{GEM_NAME}-#{GEM_VERSION}.gem}
8
+ end
9
+ namespace :dev do
10
+ desc 'Install for development (for windows)'
11
+ task :winstall => :gem do
12
+ warn "You can now call 'rake install' instead of 'rake dev:winstall'."
13
+ system %{gem install --no-rdoc --no-ri -l pkg/#{GEM_NAME}-#{GEM_VERSION}.gem}
14
+ end
15
+ end
16
+ else
17
+ task :install => :package do
18
+ sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION}.gem}
19
+ end
20
+ end