oracle-model-generator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,2 @@
1
+ = 0.1.0 - 6-Oct-2010
2
+ * Initial release.
@@ -0,0 +1,8 @@
1
+ CHANGES
2
+ MANIFEST
3
+ README
4
+ oracle-model-generator.gemspec
5
+ Rakefile
6
+ bin/omg
7
+ lib/oracle/model/generator.rb
8
+ test/test_oracle_model_generator.rb
data/README ADDED
@@ -0,0 +1,68 @@
1
+ = Description
2
+ A library for generating an ActiveRecord model from an existing Oracle table.
3
+ This will install an "omg" executable that you can use from the command line.
4
+
5
+ = Synopsis
6
+ Using the command line tool:
7
+
8
+ omg -d your_database -t your_table -u some_user -p some_password
9
+
10
+ Results in a file called "your_table.rb". This is an ActiveRecord model
11
+ declaration, with all validations, primary keys, table name and belongs_to
12
+ relationships defined.
13
+
14
+ = Requirements
15
+ == Must Have
16
+ * ruby-oci8
17
+ * getopt
18
+
19
+ == Optional
20
+ If you want to be able to avoid specifying a username and password on the
21
+ command line then you will need the dbi-dbrc library.
22
+
23
+ If you want your models to support multiple primary keys, then you will
24
+ need to install the composite_primary_keys library.
25
+
26
+ If you want date format validations, then you will need to install the
27
+ validates_timeliness library.
28
+
29
+ = What this library doesn't do
30
+ I do not attempt to set has_many or has_one relationships. There's no good
31
+ way to determine that relationship (one or many?). Besides, in practice I
32
+ find that most people set custom has_xxx relationships that go over and
33
+ above what's set in the Oracle database anyway for purposes of their
34
+ application.
35
+
36
+ I also do not go out of my way to get the model name correct with regards
37
+ to singular vs plural. I do a simple guess that covers most cases, but
38
+ complex cases will break it. It's much easier for you to rename a class or
39
+ file name than it is for me to get this 100% correct.
40
+
41
+ = Author's Comments
42
+ I chose not to patch legacy_data because I have no interest in supporting
43
+ other vendors other than Oracle with this library. By focusing only on
44
+ Oracle I could take advantage of ruby-oci8 features. In addition, I have no
45
+ interest in making this a Rails plugin, and I needed the support of multiple
46
+ primary keys.
47
+
48
+ = Future Plans
49
+ Add support for views.
50
+
51
+ = Known Issues
52
+ None known. If you find any issues, please report them on the github project
53
+ page at http://www.github.com/djberg96/oracle-model-generator.
54
+
55
+ = Warranty
56
+ This package is provided "as is" and without any express or
57
+ implied warranties, including, without limitation, the implied
58
+ warranties of merchantability and fitness for a particular purpose.
59
+
60
+ = Copyright
61
+ (C) 2010, Daniel J. Berger
62
+ All Rights Reserved
63
+
64
+ = License
65
+ Artistic 2.0
66
+
67
+ = Author
68
+ Daniel J. Berger
@@ -0,0 +1,30 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rbconfig'
4
+ include Config
5
+
6
+ namespace 'gem' do
7
+ desc 'Remove any old gem files'
8
+ task :clean do
9
+ Dir['*.gem'].each{ |f| File.delete(f) }
10
+ end
11
+
12
+ desc 'Create the oracle-model-generator gem'
13
+ task :create => :clean do
14
+ spec = eval(IO.read('oracle-model-generator.gemspec'))
15
+ Gem::Builder.new(spec).build
16
+ end
17
+
18
+ desc 'Install the oracle-model-generator gem'
19
+ task :install => [:create] do
20
+ file = Dir["oracle-model-generator*.gem"].last
21
+ sh "gem install #{file}"
22
+ end
23
+ end
24
+
25
+ Rake::TestTask.new do |t|
26
+ t.warning = true
27
+ t.verbose = true
28
+ end
29
+
30
+ task :default => :test
data/bin/omg ADDED
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'oracle/model/generator'
4
+ require 'dbi/dbrc'
5
+ require 'getopt/long'
6
+
7
+ opts = Getopt::Long.getopts(
8
+ ['--help', '-h'],
9
+ ['--table', '-t', Getopt::REQUIRED],
10
+ ['--view', '-v', Getopt::REQUIRED],
11
+ ['--user', '-u', Getopt::REQUIRED],
12
+ ['--password', '-p', Getopt::REQUIRED],
13
+ ['--database', '-d', Getopt::REQUIRED],
14
+ ['--output', '-o', Getopt::REQUIRED]
15
+ )
16
+
17
+ def help
18
+ %Q{
19
+ Available options for the Oracle Model Generator are:
20
+
21
+ -h, --help => Display the help text you're looking at now.
22
+ -t, --table => The name of the table you wish to model.
23
+ -v, --view => The name of the view you wish to model.
24
+ -o, --output => The name of the file to create.
25
+ -u, --user => The user used to establish a connection to the database.
26
+ -p, --password => The password used to establish a connection to the database.
27
+
28
+ If no user or password are supplied, then OMG will attempt to glean that
29
+ information using a combination of the database name and your .dbrc file.
30
+ If that cannot be found, then an error is raised.
31
+
32
+ If no output file is supplied then the file generated will match the name
33
+ of the table, minus the 's' if present, with a .rb extension. This is lazy,
34
+ but it is orders of magnitude easier for you to rename a file than it is
35
+ for me to deal with all possible permutations. Note that the output file
36
+ name is also used as the basis for the class name
37
+
38
+ Examples:
39
+
40
+ # Create a User model for the users table.
41
+ omg -d some_database -u scott -p tiger -t users
42
+
43
+ # Same thing, using dbi-dbrc behind the scenes
44
+ omg -d some_database -t users
45
+
46
+ # Create a Lily model for the lilies table, and specify the output file.
47
+ omg -d some_database -u scott -p tiger -t lilies -o lily.rb
48
+ }
49
+ end
50
+
51
+ if opts['h']
52
+ puts help
53
+ exit!
54
+ end
55
+
56
+ unless opts['database']
57
+ puts "You must specify a database."
58
+ exit!
59
+ end
60
+
61
+ unless opts['table'] || opts['view']
62
+ puts "You must specify a table or view."
63
+ exit!
64
+ end
65
+
66
+ user = opts['user']
67
+ pass = opts['password']
68
+
69
+ unless user && pass
70
+ begin
71
+ dbrc = DBI::DBRC.new(opts['database'], user)
72
+ user = dbrc.user
73
+ pass = dbrc.passwd
74
+ rescue DBI::DBRC::Error
75
+ msg = "No user or password provided, and no dbrc entry found for '"
76
+ msg << opts['database'] + "'."
77
+ puts msg
78
+ exit!
79
+ end
80
+ end
81
+
82
+ table = opts['table']
83
+ view = opts['view']
84
+
85
+ if table && view
86
+ puts "You cannot specify both a table and a view."
87
+ exit!
88
+ end
89
+
90
+ connection = OCI8.new(user, pass, opts['database'])
91
+ omg = Oracle::Model::Generator.new(connection)
92
+ omg.generate(table || view, view)
93
+
94
+ ofile = opts['o'] || table + '.rb'
95
+
96
+ File.open(ofile, 'w') do |fh|
97
+ fh.puts "class #{omg.table} < ActiveRecord::Base"
98
+ fh.puts " set_table_name :#{table}"
99
+
100
+ if omg.primary_keys.size > 1
101
+ fh.puts "\n # Requires the composite-primary-keys library"
102
+ fh.puts " set_primary_keys " + omg.primary_keys.inspect
103
+ else
104
+ fh.puts "\n set_primary_key " + omg.primary_keys.first.to_sym.inspect
105
+ end
106
+
107
+ fh.puts "\n # Table relationships\n\n"
108
+ omg.belongs_to.uniq.each{ |table|
109
+ fh.puts " belongs_to :#{table.downcase}"
110
+ }
111
+
112
+ fh.puts "\n # Validations\n\n"
113
+
114
+ # Character fields, size
115
+ omg.column_info.each{ |col|
116
+ data_type = col.data_type.to_s
117
+ if ['char', 'varchar', 'varchar2'].include?(data_type)
118
+ validation = "validates_size_of :#{col.name.downcase}, :maximum => #{col.data_size}"
119
+ validation << ", :allow_blank => #{col.nullable?}" if col.nullable?
120
+ fh.puts " #{validation}"
121
+ end
122
+ }
123
+
124
+ fh.puts # Line break
125
+
126
+ # Fields that must be present
127
+ omg.column_info.each{ |col|
128
+ unless col.nullable?
129
+ validation = "validates_presence_of :#{col.name.downcase}"
130
+ fh.puts " #{validation}"
131
+ end
132
+ }
133
+
134
+ fh.puts # Line break
135
+
136
+ # Numeric fields
137
+ omg.column_info.each{ |col|
138
+ if col.data_type.to_s == 'number'
139
+ max = ("9" * (col.precision - col.scale)).to_i
140
+
141
+ validation = "validates_numericality_of :#{col.name.downcase}"
142
+ validation << ", :less_than => #{max + 1}, :greater_than => -#{max + 1}"
143
+
144
+ if col.scale == 0
145
+ validation << ", :only_integer => true"
146
+ end
147
+
148
+ fh.puts " #{validation}"
149
+ end
150
+ }
151
+
152
+ fh.puts # Line break
153
+ header_printed = false
154
+
155
+ # Date fields
156
+ omg.column_info.each{ |col|
157
+ data_type = col.data_type.to_s
158
+
159
+ if ['date', 'time'].include?(data_type)
160
+ if data_type == 'date'
161
+ validation = "validates_date :#{col.name.downcase}"
162
+ end
163
+
164
+ if data_type == 'timestamp'
165
+ validation = "validates_time :#{col.name.downcase}"
166
+ end
167
+
168
+ unless header_printed
169
+ fh.puts " # Requires the validates_timeliness library"
170
+ end
171
+
172
+ fh.puts " #{validation}"
173
+ end
174
+ }
175
+
176
+ fh.puts "end"
177
+ end
178
+
179
+ puts "\nFile '#{ofile}' generated\n\n"
@@ -0,0 +1,118 @@
1
+ require 'oci8'
2
+
3
+ module Oracle
4
+ module Model
5
+ class Generator
6
+ VERSION = '0.1.0'
7
+
8
+ attr_reader :connection
9
+ attr_reader :constraints
10
+ attr_reader :foreign_keys
11
+ attr_reader :belongs_to
12
+ attr_reader :table
13
+ attr_reader :view
14
+ attr_reader :dependencies
15
+ attr_reader :column_info
16
+ attr_reader :primary_keys
17
+
18
+ # Example:
19
+ #
20
+ # connection = Oracle::Connection.new(user, password, database)
21
+ # ogenerator = Oracle::Model::Generator.new(connection)
22
+ # ogenerator.generate('users')
23
+ #
24
+ def initialize(connection)
25
+ @connection = connection
26
+ @constraints = []
27
+ @primary_keys = []
28
+ @foreign_keys = []
29
+ @dependencies = []
30
+ @belongs_to = []
31
+ @column_info = []
32
+ @table = nil
33
+ end
34
+
35
+ def generate(table, view = false)
36
+ @table = table.split('_').map{ |e| e.downcase.capitalize }.join
37
+ @view = view
38
+ get_constraints(table) unless view
39
+ get_foreign_keys unless view
40
+ get_column_info(table) unless view
41
+ get_primary_keys
42
+ end
43
+
44
+ private
45
+
46
+ def get_column_info(table_name)
47
+ table = @connection.describe_table(table_name)
48
+ table.columns.each{ |col| @column_info << col }
49
+ end
50
+
51
+ # Returns an array of primary keys.
52
+ #
53
+ def get_primary_keys
54
+ @constraints.each{ |hash|
55
+ if hash['CONSTRAINT_TYPE'] == 'P'
56
+ @primary_keys << hash['COLUMN_NAME'].downcase
57
+ end
58
+ }
59
+ end
60
+
61
+ def get_foreign_keys
62
+ @constraints.each{ |hash|
63
+ if hash['CONSTRAINT_TYPE'] == 'R'
64
+ @foreign_keys << hash['R_CONSTRAINT_NAME']
65
+ end
66
+ }
67
+
68
+ get_belongs_to()
69
+ end
70
+
71
+ def get_belongs_to
72
+ @foreign_keys.each{ |fk|
73
+ @belongs_to << find_fk_table(fk)
74
+ }
75
+ end
76
+
77
+ def find_fk_table(fk)
78
+ sql = %Q{
79
+ select table_name
80
+ from all_constraints
81
+ where constraint_name = '#{fk}'
82
+ }
83
+
84
+ begin
85
+ cursor = @connection.exec(sql)
86
+ fk = cursor.fetch.first
87
+ ensure
88
+ cursor.close if cursor
89
+ end
90
+
91
+ fk
92
+ end
93
+
94
+ # Get a list of constraints for a given table.
95
+ #
96
+ def get_constraints(table_name)
97
+ sql = %Q{
98
+ select *
99
+ from all_cons_columns a, all_constraints b
100
+ where a.owner = b.owner
101
+ and a.constraint_name = b.constraint_name
102
+ and a.table_name = b.table_name
103
+ and b.table_name = '#{table_name.upcase}'
104
+ }
105
+
106
+ begin
107
+ cursor = @connection.exec(sql)
108
+ while rec = cursor.fetch_hash
109
+ @constraints << rec
110
+ end
111
+ ensure
112
+ cursor.close if cursor
113
+ end
114
+ end
115
+
116
+ end
117
+ end
118
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'oracle/model/generator')
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'oracle-model-generator'
5
+ gem.version = '0.1.0'
6
+ gem.author = 'Daniel J. Berger'
7
+ gem.license = 'Artistic 2.0'
8
+ gem.email = 'djberg96@gmail.com'
9
+ gem.homepage = 'http://www.github.com/djberg96/oracle-model-generator'
10
+ gem.summary = 'A Ruby interface for determining protocol information'
11
+ gem.test_file = 'test/test_oracle_model_generator.rb'
12
+ gem.files = Dir['**/*'].reject{ |f| f.include?('git') }
13
+
14
+ gem.executables = "omg"
15
+ gem.rubyforge_project = 'N/A'
16
+ gem.extra_rdoc_files = %w[CHANGES README MANIFEST]
17
+
18
+ gem.add_dependency('ruby-oci8')
19
+ gem.add_dependency('getopt', '>= 1.4.0')
20
+ gem.add_development_dependency('test-unit', '>= 2.1.1')
21
+
22
+ gem.description = <<-EOF
23
+ The oracle-model-generator library allows you to generate an ActiveRecord
24
+ model from an existing Oracle table or view.
25
+ EOF
26
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ gem 'test-unit'
3
+ require 'test/unit'
4
+ require 'oracle/model/generator'
5
+
6
+ class TC_Oracle_Model_Generator < Test::Unit::TestCase
7
+ def setup
8
+ @username = 'hr'
9
+ @password = 'hr'
10
+ @database = 'xe'
11
+ @generator = nil
12
+ @connection = OCI8.new(@username, @password, @database)
13
+ end
14
+
15
+ test "version number is correct" do
16
+ assert_equal('0.1.0', Oracle::Model::Generator::VERSION)
17
+ end
18
+
19
+ test "constructor accepts an oci8 connection object" do
20
+ assert_nothing_raised{ @generator = Oracle::Model::Generator.new(@connection) }
21
+ end
22
+
23
+ test "generate method basic functionality" do
24
+ assert_nothing_raised{ @generator = Oracle::Model::Generator.new(@connection) }
25
+ assert_respond_to(@generator, :generate)
26
+ end
27
+
28
+ test "generate method works with a table name or view" do
29
+ assert_nothing_raised{ @generator = Oracle::Model::Generator.new(@connection) }
30
+ assert_nothing_raised{ @generator.generate('employees') }
31
+ assert_nothing_raised{ @generator.generate('emp_details_view', true) }
32
+ end
33
+
34
+ def teardown
35
+ @username = nil
36
+ @password = nil
37
+ @database = nil
38
+ @generator = nil
39
+ @connection.logoff if @connection
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oracle-model-generator
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Daniel J. Berger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-10-06 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: ruby-oci8
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: getopt
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 7
44
+ segments:
45
+ - 1
46
+ - 4
47
+ - 0
48
+ version: 1.4.0
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: test-unit
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ hash: 9
60
+ segments:
61
+ - 2
62
+ - 1
63
+ - 1
64
+ version: 2.1.1
65
+ type: :development
66
+ version_requirements: *id003
67
+ description: " The oracle-model-generator library allows you to generate an ActiveRecord\n model from an existing Oracle table or view.\n"
68
+ email: djberg96@gmail.com
69
+ executables:
70
+ - omg
71
+ extensions: []
72
+
73
+ extra_rdoc_files:
74
+ - CHANGES
75
+ - README
76
+ - MANIFEST
77
+ files:
78
+ - Rakefile
79
+ - README
80
+ - oracle-model-generator.gemspec
81
+ - lib/oracle/model/generator.rb
82
+ - lib/oracle_model_generator.rb
83
+ - CHANGES
84
+ - test/test_oracle_model_generator.rb
85
+ - MANIFEST
86
+ - bin/omg
87
+ has_rdoc: true
88
+ homepage: http://www.github.com/djberg96/oracle-model-generator
89
+ licenses:
90
+ - Artistic 2.0
91
+ post_install_message:
92
+ rdoc_options: []
93
+
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ hash: 3
102
+ segments:
103
+ - 0
104
+ version: "0"
105
+ required_rubygems_version: !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 3
111
+ segments:
112
+ - 0
113
+ version: "0"
114
+ requirements: []
115
+
116
+ rubyforge_project: N/A
117
+ rubygems_version: 1.3.7
118
+ signing_key:
119
+ specification_version: 3
120
+ summary: A Ruby interface for determining protocol information
121
+ test_files:
122
+ - test/test_oracle_model_generator.rb