database_introspection 0.0.9
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.
- checksums.yaml +7 -0
- data/.gitignore +47 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +107 -0
- data/Rakefile +1 -0
- data/database_introspection.gemspec +30 -0
- data/lib/database_introspection/dynamic_model/active_record_extension.rb +39 -0
- data/lib/database_introspection/dynamic_model/domain_extension.rb +28 -0
- data/lib/database_introspection/dynamic_model/managed_domains.rb +23 -0
- data/lib/database_introspection/dynamic_model/migration.rb +13 -0
- data/lib/database_introspection/dynamic_model/relations_analyser.rb +163 -0
- data/lib/database_introspection/dynamic_model/tables_analyser.rb +56 -0
- data/lib/database_introspection/dynamic_model.rb +31 -0
- data/lib/database_introspection/version.rb +3 -0
- data/lib/database_introspection.rb +13 -0
- metadata +145 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: cb2ff4ce8131add8f4e7cfc4830240d8ba45df98
|
4
|
+
data.tar.gz: 524b268bdb6f8f11bd6ef6798c00dbf5c5e34bff
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac56205a6698c903436d4d5935861abad37fce34a7fc0e3652b5f33def754270fb71af500e4dff03a77d9a5ac93efd6456e29787ca0036302cfc83f5d2fc397c
|
7
|
+
data.tar.gz: 3f8fd05f31a29d3cecf0b72d29427c1d5c9121cb42736a96cde92a49bff786f62026ac59e1e6c893ca619dd28a989503bc0050be6026c5c040c703dc059edf70
|
data/.gitignore
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
# Standard
|
19
|
+
*~
|
20
|
+
# Standard Rails project
|
21
|
+
/tmp/
|
22
|
+
/log/
|
23
|
+
/db/*.sqlite3
|
24
|
+
# SASS CSS generation
|
25
|
+
/public/stylesheets/.sass-cache/
|
26
|
+
# Netbeans
|
27
|
+
/nbproject/
|
28
|
+
# Sublime Text 2 project
|
29
|
+
*.sublime-project
|
30
|
+
*.sublime-workspace
|
31
|
+
*(copie)*
|
32
|
+
# RVM
|
33
|
+
.rvmrc
|
34
|
+
# VisualRuby
|
35
|
+
.vr_settings.yaml
|
36
|
+
# Emacs
|
37
|
+
*#
|
38
|
+
*\#
|
39
|
+
\#*
|
40
|
+
.#*
|
41
|
+
\#*\#
|
42
|
+
# Geany
|
43
|
+
*.geany
|
44
|
+
# RubyMine
|
45
|
+
.idea
|
46
|
+
#RedCar
|
47
|
+
.redcar
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Laurent
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Database Introspection
|
2
|
+
|
3
|
+
This gem will introspect the database and create __dynamically__ ActiveRecord::Base descendants that can be used by your application, including some Rails associations helper methods.
|
4
|
+
|
5
|
+
It is intended to be primarily used within rails applications but nothing prevents you to use standalone, provided the fact you are already connected to a database.
|
6
|
+
This gem does a bit the reverse action of what you do with Rails generator, when you want to generate the database from you migrations.
|
7
|
+
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'database_introspection'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install database_introspection
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
### Basic database introspection
|
26
|
+
|
27
|
+
To introspect the database, you just have to call the following method:
|
28
|
+
|
29
|
+
```ruby
|
30
|
+
DynamicModel.introspect_database
|
31
|
+
```
|
32
|
+
|
33
|
+
By default it will analyse all database tables starting by "user_defined_", and will create ActiveRecord::Base descendant to handle them.
|
34
|
+
|
35
|
+
For example if your database contains the following tables:
|
36
|
+
|
37
|
+
```
|
38
|
+
user_defined_table1
|
39
|
+
user_defined_table2
|
40
|
+
user_defined_table3
|
41
|
+
user_defined_table4
|
42
|
+
```
|
43
|
+
The call to `DynamicModel.introspect_database`, will inject the following classes in your application:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
DynamicModel::ManagedDomains::UserDefined::Table1
|
47
|
+
DynamicModel::ManagedDomains::UserDefined::Table2
|
48
|
+
DynamicModel::ManagedDomains::UserDefined::Table3
|
49
|
+
DynamicModel::ManagedDomains::UserDefined::Table4
|
50
|
+
```
|
51
|
+
|
52
|
+
### Architecture
|
53
|
+
|
54
|
+
#### Classes and modules generated
|
55
|
+
|
56
|
+
* `DynamicModel` is the module that contains methods to introspect the database.
|
57
|
+
* `DynamicModel::ManagedDomain` contains some methods to manipulate the domains introspected.
|
58
|
+
* Then for example in the first example, `DynamicModel::ManagedDomains::UserDefined` is a module created dynamically from the domain name (ie tables prefix). It contains itself some methods to easily manipulate the tables or generated classes of this particular domain.
|
59
|
+
* The `DynamicModel::ManagedDomains::UserDefined::Tablex` classes are descendants of ActiveRecord::Base that you will use in your application.
|
60
|
+
|
61
|
+
Of course if you provide another domain name (ie tables prefix) the corresponding modules and classes will be created accordingly. Running:
|
62
|
+
|
63
|
+
```ruby
|
64
|
+
DynamicModel.introspect_database :another_domain
|
65
|
+
```
|
66
|
+
|
67
|
+
On a database containing the following tables:
|
68
|
+
|
69
|
+
```
|
70
|
+
another_domain_table1
|
71
|
+
another_domain_table2
|
72
|
+
```
|
73
|
+
|
74
|
+
Will inject the following classes in your application:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
DynamicModel::ManagedDomains::AnotherDomain::Table1
|
78
|
+
DynamicModel::ManagedDomains::AnotherDomain::Table2
|
79
|
+
```
|
80
|
+
and of course the following module:
|
81
|
+
```ruby
|
82
|
+
DynamicModel::ManagedDomains::AnotherDomain
|
83
|
+
```
|
84
|
+
|
85
|
+
#### Generated classes
|
86
|
+
|
87
|
+
The classes generated will actually have some behaviour added by extending the module `DynamicModel::ActiveRecordExtension` to basically be aware of the domain they belong to.
|
88
|
+
|
89
|
+
### Database relationships
|
90
|
+
|
91
|
+
Provided you follow the standard rails rules for ids, for example:
|
92
|
+
|
93
|
+
if `user_defined_table1` contains `user_defined_table2_id` (not yet implemented) or `table2_id`, the introspector will understand there is a relationship between the tables and create the ActiveRecord associations accordingly adding all the standard helper methods to the generated classes !
|
94
|
+
|
95
|
+
## To do
|
96
|
+
|
97
|
+
* Improve Readme.
|
98
|
+
* Add code comments.
|
99
|
+
* Improve table relationship introspection.
|
100
|
+
|
101
|
+
## Contributing
|
102
|
+
|
103
|
+
1. Fork it
|
104
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
105
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
106
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
107
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'database_introspection/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "database_introspection"
|
8
|
+
spec.version = DatabaseIntrospection::VERSION
|
9
|
+
spec.authors = ["L.Briais"]
|
10
|
+
spec.email = ["lbnetid+rb@gmail.com"]
|
11
|
+
spec.description = %q{This gem will generate classes (by default ActiveRecord::Base descendants) from database introspection}
|
12
|
+
spec.summary = %q{Database Introspection}
|
13
|
+
spec.homepage = "https://github.com/lbriais/database_introspection"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_runtime_dependency "activemodel", ">= 3.2.13"
|
25
|
+
spec.add_runtime_dependency "activerecord", ">= 3.2.13"
|
26
|
+
spec.add_runtime_dependency "activeresource", ">= 3.2.13"
|
27
|
+
spec.add_runtime_dependency "activesupport", ">= 3.2.13"
|
28
|
+
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module DynamicModel::ActiveRecordExtension
|
2
|
+
|
3
|
+
def self.included(base) # :nodoc:
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
# Be very careful with what is added here as it may conflict with whatever is generated by active record.
|
8
|
+
module InstanceMethods
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
# This one, only for the class
|
13
|
+
module ClassMethods
|
14
|
+
def to_param
|
15
|
+
"#{self.name_space}/#{self.list_name}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def display_name
|
19
|
+
self.name.gsub(/^.*::([^:]+)$/, "\\1").titleize
|
20
|
+
end
|
21
|
+
|
22
|
+
def domain
|
23
|
+
puts name.gsub(/^.*::[^:]+$/, '')
|
24
|
+
name.gsub(/::[^:]+$/, '').constantize
|
25
|
+
end
|
26
|
+
|
27
|
+
def name_space
|
28
|
+
self.name.gsub( /DynamicModel::ManagedDomains::([^:]+)::.*$/, "\\1") .underscore
|
29
|
+
end
|
30
|
+
|
31
|
+
def list_name
|
32
|
+
self.name.gsub( /^.*::([^:]+)$/, "\\1") .underscore.pluralize
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
include InstanceMethods
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module DynamicModel::DomainExtension
|
4
|
+
|
5
|
+
def model_classes
|
6
|
+
self.constants.map {|sym| "#{self.name}::#{sym.to_s}".constantize}
|
7
|
+
end
|
8
|
+
|
9
|
+
def table_names
|
10
|
+
ActiveRecord::Base.connection.tables.grep(/^#{prefix}_/)
|
11
|
+
end
|
12
|
+
|
13
|
+
def scoped_table_names
|
14
|
+
table_names.map{|table_name| table_name.gsub /^#{prefix}_/, ''}
|
15
|
+
end
|
16
|
+
|
17
|
+
def prefix
|
18
|
+
DynamicModel::ManagedDomains.to_hash[self]
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_table(scoped_table_name, &block)
|
22
|
+
DynamicModel.add_table scoped_table_name, table_prefix: prefix, &block
|
23
|
+
end
|
24
|
+
|
25
|
+
def model_class(scoped_table_name)
|
26
|
+
Hash[scoped_table_names.zip model_classes][scoped_table_name]
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Namespace for domains
|
2
|
+
module DynamicModel::ManagedDomains
|
3
|
+
def self.domain_prefixes
|
4
|
+
constants.map {|c| c.to_s.underscore}
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.domain_modules
|
8
|
+
constants.map {|c| "#{self.name}::#{c.to_s}".constantize}
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.to_hash
|
12
|
+
Hash[domain_modules.zip domain_prefixes]
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.tables
|
16
|
+
self.domain_modules.map {|mod| mod.table_names}.flatten
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.domain_module(table_prefix)
|
20
|
+
Hash[domain_prefixes.zip domain_modules][table_prefix]
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
class DynamicModel::Migration < ActiveRecord::Migration
|
3
|
+
def self.create_with(name, &block)
|
4
|
+
create_table name.to_sym do |t|
|
5
|
+
block.call(t) if block_given?
|
6
|
+
begin
|
7
|
+
t.timestamps
|
8
|
+
rescue
|
9
|
+
puts "Cannot create timestamps... Probably already created."
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
class DynamicModel::RelationsAnalyser
|
3
|
+
KEY_IDENTIFIER = /_id$/
|
4
|
+
|
5
|
+
attr_reader :alterations
|
6
|
+
|
7
|
+
def initialize(klasses)
|
8
|
+
@klasses = klasses
|
9
|
+
@alterations = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
raise "cannot rerun analysis with the same object... Create a new instance !" if @did_run
|
14
|
+
return if @klasses.empty?
|
15
|
+
@domain = @klasses[0].domain
|
16
|
+
introspect_belongs_to
|
17
|
+
verify_if_has_many_relations_could_be_actually_has_one
|
18
|
+
discover_has_many_through_from_belongs_to
|
19
|
+
apply_alterations
|
20
|
+
@did_run = true
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def discover_has_many_through_from_belongs_to
|
27
|
+
puts "Has_many_through analysis started."
|
28
|
+
@alterations.each do |model, alterations|
|
29
|
+
alterations[:has_many_though] ||= []
|
30
|
+
alterations.each do |association_type, associations|
|
31
|
+
next unless association_type == :belongs_to
|
32
|
+
# If there is only one belongs_to, there cannot be a has_many_through
|
33
|
+
next if associations.size < 2
|
34
|
+
analyses_has_many_through_association model, associations
|
35
|
+
end
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
puts "Has_many_though analysis completed."
|
39
|
+
end
|
40
|
+
|
41
|
+
def introspect_belongs_to
|
42
|
+
puts "Belongs_to analysis started."
|
43
|
+
scoped_table_names_hash = Hash[@domain.scoped_table_names.zip @domain.model_classes]
|
44
|
+
@klasses.each do |klass|
|
45
|
+
@alterations[klass] ||= {}
|
46
|
+
# Find attributes ending by "_id"
|
47
|
+
klass.attribute_names.grep(KEY_IDENTIFIER) do |attr_name|
|
48
|
+
if klass.columns_hash[attr_name].type == :integer
|
49
|
+
# Check if there is a table in the domain that may be linked to this field
|
50
|
+
candidate_table_name = attr_name.gsub(KEY_IDENTIFIER, '').pluralize
|
51
|
+
candidate_target_class = scoped_table_names_hash[candidate_table_name]
|
52
|
+
# Creates a belongs_to relation
|
53
|
+
if scoped_table_names_hash.keys.include? candidate_table_name
|
54
|
+
@alterations[klass][:belongs_to] ||= []
|
55
|
+
@alterations[klass][:belongs_to] << {
|
56
|
+
key: attr_name,
|
57
|
+
class: candidate_target_class
|
58
|
+
}
|
59
|
+
# and the reverse, by default has_many
|
60
|
+
@alterations[candidate_target_class] ||= {}
|
61
|
+
@alterations[candidate_target_class][:has_many] ||= []
|
62
|
+
@alterations[candidate_target_class][:has_many] << {
|
63
|
+
class: klass
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
ensure
|
70
|
+
puts "Belongs_to analysis completed."
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def verify_if_has_many_relations_could_be_actually_has_one
|
75
|
+
puts "Has_many analysis started."
|
76
|
+
@alterations.each do |model, alterations|
|
77
|
+
alterations[:has_one] ||= []
|
78
|
+
alterations.each do |association_type, associations|
|
79
|
+
next unless association_type == :has_many
|
80
|
+
associations.map! do |description|
|
81
|
+
if analyses_has_many_association model, description
|
82
|
+
# This is actually a has_one
|
83
|
+
alterations[:has_one] << description
|
84
|
+
nil
|
85
|
+
else
|
86
|
+
description
|
87
|
+
end
|
88
|
+
end
|
89
|
+
associations.compact!
|
90
|
+
end
|
91
|
+
end
|
92
|
+
ensure
|
93
|
+
puts "Has_many analysis completed."
|
94
|
+
end
|
95
|
+
|
96
|
+
def analyses_has_many_through_association(model, associations)
|
97
|
+
# As there are multiple belongs_to in this class, all combinations
|
98
|
+
# should lead to a has_many_through
|
99
|
+
# Waouh, Ruby rocks !!
|
100
|
+
associations.combination(2).each do |left, right|
|
101
|
+
@alterations[left[:class]][:has_many_through] ||= []
|
102
|
+
@alterations[right[:class]][:has_many_through] ||= []
|
103
|
+
|
104
|
+
|
105
|
+
@alterations[left[:class]][:has_many_through] << {
|
106
|
+
self_key: left[:key],
|
107
|
+
key: right[:key],
|
108
|
+
middle_class: model,
|
109
|
+
class: right[:class]
|
110
|
+
}
|
111
|
+
@alterations[right[:class]][:has_many_through] << {
|
112
|
+
self_key: right[:key],
|
113
|
+
key: left[:key],
|
114
|
+
middle_class: model,
|
115
|
+
class: left[:class]
|
116
|
+
}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def analyses_has_many_association(model, description)
|
121
|
+
# If one day I figure out how to determine if a has_many relation could be a has_one,
|
122
|
+
# should be implemented here... Doesn't look like solvable...
|
123
|
+
false
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def apply_alterations
|
128
|
+
@alterations.each do |model, alterations|
|
129
|
+
puts "Processing alterations for #{model.list_name}"
|
130
|
+
alterations.each do |association_type, associations|
|
131
|
+
associations.each do |description|
|
132
|
+
method_name = "add_#{association_type}_behaviour"
|
133
|
+
self.send method_name, model, description
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
def add_belongs_to_behaviour(model, description)
|
141
|
+
field_name = description[:key].gsub KEY_IDENTIFIER, ''
|
142
|
+
model.belongs_to field_name, :foreign_key => description[:key], :class_name => description[:class].name
|
143
|
+
puts " - belongs_to :#{field_name}, :foreign_key => #{description[:key]}, :class_name => #{description[:class].name}"
|
144
|
+
end
|
145
|
+
|
146
|
+
def add_has_many_behaviour(model, description)
|
147
|
+
field_name = description[:class].list_name
|
148
|
+
model.has_many field_name, :class_name => description[:class].name
|
149
|
+
puts " - has_many :#{field_name}, :class_name => #{description[:class].name}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def add_has_many_through_behaviour(model, description)
|
153
|
+
puts " - has_many #{description[:class]} through #{description[:middle_class]}"
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
def add_has_one_behaviour(model, description)
|
158
|
+
field_name = description[:class].list_name.singularize
|
159
|
+
model.has_one field_name, :class_name => description[:class].name
|
160
|
+
puts " - has_one :#{field_name}, :class_name => #{description[:class].name}"
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
class DynamicModel::TablesAnalyser
|
4
|
+
|
5
|
+
attr_reader :domain, :klasses_analysed
|
6
|
+
|
7
|
+
def initialize(domain, base_class = ActiveRecord::Base)
|
8
|
+
@domain = domain
|
9
|
+
@base_class = base_class
|
10
|
+
define_domain_module
|
11
|
+
@klasses_analysed = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def scan_database
|
15
|
+
# Introspect database tables and creates ActiveRecord descendants in the name space module
|
16
|
+
@domain_module.table_names.map do |table_name|
|
17
|
+
inject_class table_name
|
18
|
+
end
|
19
|
+
@domain_module.model_classes
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
|
25
|
+
def inject_class(table_name)
|
26
|
+
short_model_name = table_name.gsub(/#{@domain_module.prefix}_/, '').singularize.camelize
|
27
|
+
klass = nil
|
28
|
+
if @domain_module.constants.include? short_model_name.to_sym
|
29
|
+
klass = @domain_module.const_get short_model_name
|
30
|
+
puts "Found #{klass.name} that already handles #{table_name}"
|
31
|
+
else
|
32
|
+
klass = @domain_module.const_set short_model_name, Class.new(@base_class)
|
33
|
+
puts "Created #{klass.name} to handle #{table_name}"
|
34
|
+
# Adds some class methods
|
35
|
+
klass.send :include, DynamicModel::ActiveRecordExtension
|
36
|
+
end
|
37
|
+
|
38
|
+
# Maps the class to the correct table
|
39
|
+
klass.table_name = table_name
|
40
|
+
# Adds attributes accessible for mass assign
|
41
|
+
klass.attr_accessible *(klass.attribute_names - [klass.primary_key])
|
42
|
+
end
|
43
|
+
|
44
|
+
def define_domain_module
|
45
|
+
domain_name = @domain.singularize.camelize
|
46
|
+
@domain_module = nil
|
47
|
+
if DynamicModel::ManagedDomains.constants.include? domain_name.to_sym
|
48
|
+
@domain_module = DynamicModel::ManagedDomains.const_get domain_name
|
49
|
+
else
|
50
|
+
@domain_module = DynamicModel::ManagedDomains.const_set(domain_name, Module.new)
|
51
|
+
@domain_module.extend DynamicModel::DomainExtension
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
module DynamicModel
|
4
|
+
|
5
|
+
# Creates ActiveRecord::Base descendants from the database.
|
6
|
+
# ActiveRecord descendant classes are dynamically created from database introspection in their own namespace
|
7
|
+
# (DynamicModel::<NameSpace>::) The name of the module NameSpace is derived from table_prefix.
|
8
|
+
def self.introspect_database(table_prefix = :user_defined, base_class = ActiveRecord::Base)
|
9
|
+
table_prefix = table_prefix.to_s
|
10
|
+
@domain_analyser ||= {}
|
11
|
+
|
12
|
+
# Confines Activerecord classes into a module named from table_prefix
|
13
|
+
@domain_analyser[table_prefix] ||= DynamicModel::TablesAnalyser.new table_prefix, base_class
|
14
|
+
klasses = @domain_analyser[table_prefix].scan_database
|
15
|
+
relation_analyser = RelationsAnalyser.new klasses
|
16
|
+
relation_analyser.run
|
17
|
+
end
|
18
|
+
|
19
|
+
# Creates a new table with auto numbered id in the database with the name prefix provided
|
20
|
+
# by creating a live migration.
|
21
|
+
# If block is provided it will behave like create_table method for migrations, allowing
|
22
|
+
# to create any other column.
|
23
|
+
def self.add_table(scoped_table_name, table_prefix: :user_defined, &block)
|
24
|
+
scoped_table_name = scoped_table_name.to_s
|
25
|
+
table_prefix = table_prefix.to_s
|
26
|
+
real_table_name = scoped_table_name.underscore.pluralize
|
27
|
+
Migration::create_with "#{table_prefix}_#{real_table_name}", &block
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'database_introspection/version'
|
2
|
+
require 'database_introspection/dynamic_model'
|
3
|
+
require 'database_introspection/dynamic_model/active_record_extension'
|
4
|
+
require 'database_introspection/dynamic_model/domain_extension'
|
5
|
+
require 'database_introspection/dynamic_model/managed_domains'
|
6
|
+
require 'database_introspection/dynamic_model/tables_analyser'
|
7
|
+
require 'database_introspection/dynamic_model/relations_analyser'
|
8
|
+
require 'database_introspection/dynamic_model/migration'
|
9
|
+
|
10
|
+
|
11
|
+
module DatabaseIntrospection
|
12
|
+
# Your code goes here...
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: database_introspection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- L.Briais
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-05-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activemodel
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.13
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.2.13
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activerecord
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.2.13
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.2.13
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: activeresource
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 3.2.13
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 3.2.13
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: activesupport
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 3.2.13
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.2.13
|
97
|
+
description: This gem will generate classes (by default ActiveRecord::Base descendants)
|
98
|
+
from database introspection
|
99
|
+
email:
|
100
|
+
- lbnetid+rb@gmail.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- .gitignore
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- database_introspection.gemspec
|
111
|
+
- lib/database_introspection.rb
|
112
|
+
- lib/database_introspection/dynamic_model.rb
|
113
|
+
- lib/database_introspection/dynamic_model/active_record_extension.rb
|
114
|
+
- lib/database_introspection/dynamic_model/domain_extension.rb
|
115
|
+
- lib/database_introspection/dynamic_model/managed_domains.rb
|
116
|
+
- lib/database_introspection/dynamic_model/migration.rb
|
117
|
+
- lib/database_introspection/dynamic_model/relations_analyser.rb
|
118
|
+
- lib/database_introspection/dynamic_model/tables_analyser.rb
|
119
|
+
- lib/database_introspection/version.rb
|
120
|
+
homepage: https://github.com/lbriais/database_introspection
|
121
|
+
licenses:
|
122
|
+
- MIT
|
123
|
+
metadata: {}
|
124
|
+
post_install_message:
|
125
|
+
rdoc_options: []
|
126
|
+
require_paths:
|
127
|
+
- lib
|
128
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - '>='
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
requirements: []
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 2.0.0.rc.2
|
141
|
+
signing_key:
|
142
|
+
specification_version: 4
|
143
|
+
summary: Database Introspection
|
144
|
+
test_files: []
|
145
|
+
has_rdoc:
|