model_probe 1.0.7 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 256df9f2a0d516dc276210959300379bdf59a743
4
- data.tar.gz: ec8afcc2bafbf1d2bf86ef0c2529bf0cc4c78316
2
+ SHA256:
3
+ metadata.gz: fedda5d159cc8bbc6bd0181bf718555437137143a893ca70c0ebacc9cdb1bf50
4
+ data.tar.gz: ec4be05cd44a4824637e062a538234327beaf5644630565beedbfd2c520becb6
5
5
  SHA512:
6
- metadata.gz: 156afe3b29d19de7bda2b5ade402b84827f250e2285ad02f23f2808e1122f88aafc8611f5d3dda292bed8de34127bbedc30e0de78d0c50a17099afa47ee3eba6
7
- data.tar.gz: a0d678decaac686cfb1d234436f2504be0b2d54d67f4343d551771e34c19baa0904b41784be02acaf7d519d8322a15ddc61321130a4db7c729f115f3aea2cc05
6
+ metadata.gz: ce89a216d52fbace263249b4874c674cc94302305acd38205f2e5f57861cb2af086fb414b7fcd692171732cda07151c8874c4d690579447c25850d30cc700e11
7
+ data.tar.gz: a30aac6054f3c4beca2d7b745b4628fe9b654fc0265da4c498ceb3e8b9f7385d3f8aac3e19962a4811be5ac487a2ae4f22f66cf1907c8ae5c7a905024e7e0897
data/Gemfile CHANGED
@@ -1,2 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  gemspec
data/Gemfile.lock CHANGED
@@ -1,19 +1,62 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- model_probe (1.0.7)
4
+ model_probe (1.1.1)
5
+ rainbow
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- rake (10.4.2)
10
+ ast (2.4.2)
11
+ json (2.6.3)
12
+ language_server-protocol (3.17.0.3)
13
+ lint_roller (1.0.0)
14
+ magic_frozen_string_literal (1.2.0)
15
+ parallel (1.23.0)
16
+ parser (3.2.2.1)
17
+ ast (~> 2.4.1)
18
+ rainbow (3.1.1)
19
+ regexp_parser (2.8.0)
20
+ rexml (3.2.5)
21
+ rubocop (1.52.0)
22
+ json (~> 2.3)
23
+ parallel (~> 1.10)
24
+ parser (>= 3.2.0.0)
25
+ rainbow (>= 2.2.2, < 4.0)
26
+ regexp_parser (>= 1.8, < 3.0)
27
+ rexml (>= 3.2.5, < 4.0)
28
+ rubocop-ast (>= 1.28.0, < 2.0)
29
+ ruby-progressbar (~> 1.7)
30
+ unicode-display_width (>= 2.4.0, < 3.0)
31
+ rubocop-ast (1.29.0)
32
+ parser (>= 3.2.1.0)
33
+ rubocop-performance (1.18.0)
34
+ rubocop (>= 1.7.0, < 2.0)
35
+ rubocop-ast (>= 0.4.0)
36
+ ruby-progressbar (1.13.0)
37
+ standard (1.29.0)
38
+ language_server-protocol (~> 3.17.0.2)
39
+ lint_roller (~> 1.0)
40
+ rubocop (~> 1.52.0)
41
+ standard-custom (~> 1.0.0)
42
+ standard-performance (~> 1.1.0)
43
+ standard-custom (1.0.1)
44
+ lint_roller (~> 1.0)
45
+ standard-performance (1.1.0)
46
+ lint_roller (~> 1.0)
47
+ rubocop-performance (~> 1.18.0)
48
+ standardrb (1.0.1)
49
+ standard
50
+ unicode-display_width (2.4.2)
10
51
 
11
52
  PLATFORMS
12
- ruby
53
+ arm64-darwin-21
54
+ arm64-darwin-22
13
55
 
14
56
  DEPENDENCIES
57
+ magic_frozen_string_literal
15
58
  model_probe!
16
- rake
59
+ standardrb
17
60
 
18
61
  BUNDLED WITH
19
- 1.16.0
62
+ 2.3.5
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Nathan Hopkins
1
+ Copyright (c) 2023 Nathan Hopkins
2
2
 
3
3
  MIT License
4
4
 
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
19
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
20
  LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
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.
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,140 +1,74 @@
1
1
  # ModelProbe
2
2
 
3
- <a target='_blank' rel='nofollow' href='https://app.codesponsor.io/link/QMSjMHrtPhvfmCnk5Hbikhhr/hopsoft/model_probe'>
4
- <img alt='Sponsor' width='888' height='68' src='https://app.codesponsor.io/embed/QMSjMHrtPhvfmCnk5Hbikhhr/hopsoft/model_probe.svg' />
5
- </a>
6
-
7
- ## Schema introspection for ActiveRecord
8
-
9
- Provides a detailed view of the underlying schema that backs an ActiveRecord model.
10
-
11
- *This functionality can be added to any object that implements [ActiveRecord's columns interface](http://rubydoc.info/docs/rails/ActiveRecord/ModelSchema/ClassMethods#columns-instance_method).*
12
-
13
- ## Installation
14
-
15
- Add this line to your application's Gemfile:
16
-
17
- gem 'model_probe'
18
-
19
- And then execute:
20
-
21
- $ bundle
22
-
23
- Or install it yourself as:
24
-
25
- $ gem install model_probe
26
-
27
- ## Usage
28
-
29
- ```ruby
30
- MyModel.extend ModelProbe
31
- MyModel.probe
32
- MyModel.print_fixture
33
- MyModel.print_model
34
- ```
35
-
36
- ## Rails Integration
37
-
38
- ModelProbe auto initializes in the Rails development environment.
39
- This means your models are implicitly extended with this behavior when developing.
40
-
41
- It also ships with these convenient rake tasks.
42
-
43
- ```sh
44
- rails t -T model_probe
45
-
46
- # rails model_probe:print_fixture[klass] # Print fixture
47
- # rails model_probe:print_model[klass] # Print model
48
- # rails model_probe:probe[klass] # Probe
49
- ```
50
-
51
- ```sh
52
- rails model_probe:probe[User]
53
-
54
- # confirmation_sent_at datetime..timestamp without time zone NULL
55
- # confirmation_token string....character varying NULL
56
- # confirmed_at datetime..timestamp without time zone NULL
57
- # created_at datetime..timestamp without time zone
58
- # current_sign_in_at datetime..timestamp without time zone NULL
59
- # current_sign_in_ip inet......inet NULL
60
- # email string....character varying NULL []
61
- # encrypted_password string....character varying []
62
- # failed_attempts integer...integer [0]
63
- # * id uuid......uuid
64
- # last_sign_in_at datetime..timestamp without time zone NULL
65
- # last_sign_in_ip inet......inet NULL
66
- # locked_at datetime..timestamp without time zone NULL
67
- # payment_platform string....character varying NULL
68
- # payment_platform_id string....character varying NULL
69
- # phone_number_id uuid......uuid
70
- # remember_created_at datetime..timestamp without time zone NULL
71
- # reset_password_sent_at datetime..timestamp without time zone NULL
72
- # reset_password_token string....character varying NULL
73
- # sign_in_count integer...integer [0]
74
- # unconfirmed_email string....character varying NULL
75
- # unlock_token string....character varying NULL
76
- # updated_at datetime..timestamp without time zone
77
- ```
78
-
79
- ```sh
80
- rails model_probe:print_fixture[User]
81
-
82
- # ---
83
- # user:
84
- # confirmation_sent_at: value
85
- # confirmation_token: value
86
- # confirmed_at: value
87
- # current_sign_in_at: value
88
- # current_sign_in_ip: value
89
- # email: ''
90
- # encrypted_password: ''
91
- # failed_attempts: '0'
92
- # last_sign_in_at: value
93
- # last_sign_in_ip: value
94
- # locked_at: value
95
- # payment_platform: value
96
- # payment_platform_id: value
97
- # phone_number_id: value
98
- # reset_password_sent_at: value
99
- # reset_password_token: value
100
- # sign_in_count: '0'
101
- # unconfirmed_email: value
102
- # unlock_token: value
103
- ```
104
-
105
- ```sh
106
- rails model_probe:print_model[User]
107
-
108
- # class User < ApplicationRecord
109
- # # extends ...................................................................
110
- # # includes ..................................................................
111
- #
112
- # # relationships .............................................................
113
- # belongs_to :payment_platform
114
- # belongs_to :phone_number
115
- #
116
- # # validations ...............................................................
117
- # validates :created_at, presence: true
118
- # validates :encrypted_password, presence: true
119
- # validates :failed_attempts, presence: true
120
- # validates :phone_number_id, presence: true
121
- # validates :sign_in_count, presence: true
122
- # validates :updated_at, presence: true
123
- #
124
- # # callbacks .................................................................
125
- # # scopes ....................................................................
126
- # # additional config (i.e. accepts_nested_attribute_for etc...) ..............
127
- #
128
- # # class methods .............................................................
129
- # class << self
130
- # end
131
- #
132
- # # public instance methods ...................................................
133
- #
134
- # # protected instance methods ................................................
135
- # protected
136
- #
137
- # # private instance methods ..................................................
138
- # private
139
- # end
140
- ```
3
+ ## ActiveRecord schema visualization and model organization made easy 🙌
4
+
5
+ Colorized table info for columns, types, nullables, indexes...
6
+ and the actual DDL used by the database to create the table.
7
+ _All this and more with ModelProbe!_
8
+
9
+ 1. Get a clear picture of your model's underlying schema with beautiful and informative schema introspection.
10
+ 1. Generate model class definitions with a well organized, logical structure.
11
+ 1. Create sensible text fixture stubs.
12
+
13
+ <!-- Tocer[start]: Auto-generated, don't remove. -->
14
+
15
+ ## Table of Contents
16
+
17
+ - [Quick Start](#quick-start)
18
+ - [Supported Databases](#supported-databases)
19
+ - [Videos](#videos)
20
+ - [Screenshots](#screenshots)
21
+
22
+ <!-- Tocer[finish]: Auto-generated, don't remove. -->
23
+
24
+ ## Quick Start
25
+
26
+ 1. Add the GEM to your project
27
+
28
+ ```sh
29
+ bundle add model_probe
30
+ ```
31
+
32
+ _ModelProbe auto initializes in the Rails `development` environment._
33
+
34
+ 1. Use in a Rails console
35
+
36
+ ```ruby
37
+ # examples for a User model
38
+ User.probe
39
+ User.print_model
40
+ User.print_fixture
41
+ ```
42
+ 1. Use with Rails (Rake) tasks
43
+
44
+ ```sh
45
+ # examples for a User model
46
+ bin/rails model_probe:probe[User]
47
+ bin/rails model_probe:print_model[User]
48
+ bin/rails model_probe:print_fixture[User]
49
+ ```
50
+
51
+ ## Supported Databases
52
+
53
+ - MySQL
54
+ - PostgreSQL
55
+ - SQLite
56
+ - _...more? contributions welcome ;)_
57
+
58
+ ## Videos
59
+
60
+ [![image](https://ik.imagekit.io/hopsoft/model_probe_intro_Zf04NFJ1-.webp?updatedAt=1683471619001)](https://youtu.be/Q3IdyKateQE)
61
+
62
+ ## Screenshots
63
+
64
+ Introspect your ActiveRecord models to build a deep understanding of the underlying database structure.
65
+
66
+ ![ModelProbe probe](https://ik.imagekit.io/hopsoft/mode_probe_probe_3ouJjft48.webp?updatedAt=1683465723169)
67
+
68
+ Generate a well organized template for your ActiveRecord model's class definition.
69
+
70
+ ![ModelProbe print_model](https://ik.imagekit.io/hopsoft/model_probe_print_model_sGOZWw-D5.webp?updatedAt=1683465723049)
71
+
72
+ Create fixture stubs to use in the test suite.
73
+
74
+ ![ModelProbe print_fixture](https://ik.imagekit.io/hopsoft/model_probe_print_fixture_ZZ2TavUO7.webp?updatedAt=1683465722977)
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelProbe::Probes::Columns
4
+ def probe_columns
5
+ name_pad = columns.map { |c| c.name.length }.max + 2
6
+ type_pad = columns.map { |c| c.type.length }.max + 2
7
+ sql_type_pad = columns.map { |c| c.sql_type.length }.max + 1
8
+ columns.sort_by(&:name).each do |column|
9
+ probe_column column, name_pad: name_pad, type_pad: type_pad, sql_type_pad: sql_type_pad
10
+ end
11
+ end
12
+
13
+ protected
14
+
15
+ def primary_key_columns
16
+ columns.select { |column| primary_key_column? column }.sort_by(&:name)
17
+ end
18
+
19
+ def foreign_key_columns
20
+ columns.select { |column| foreign_key_column? column }.sort_by(&:name)
21
+ end
22
+
23
+ def relation_columns
24
+ columns.select { |column| relation_column? column }.sort_by(&:name)
25
+ end
26
+
27
+ def required_columns
28
+ columns.select { |column| required_column? column }.sort_by(&:name)
29
+ end
30
+
31
+ def limit_columns
32
+ columns.select { |column| limit_column? column }.sort_by(&:name)
33
+ end
34
+
35
+ def validation_columns
36
+ (required_columns + limit_columns).uniq.sort_by(&:name)
37
+ end
38
+
39
+ private
40
+
41
+ def probe_column(column, name_pad:, type_pad:, sql_type_pad:)
42
+ name = column.name
43
+ if primary_key_column?(column)
44
+ print Rainbow("*#{name}".rjust(name_pad)).magenta.bright
45
+ else
46
+ print Rainbow(name.to_s.rjust(name_pad)).magenta
47
+ end
48
+ print " "
49
+ print column.type.to_s.ljust(type_pad, ".")
50
+ print Rainbow(column.sql_type.to_s.ljust(sql_type_pad)).dimgray
51
+ print Rainbow("NULLABLE ").dimgray.faint if column.null
52
+ print Rainbow("REQUIRED ").indianred unless column.null
53
+ print Rainbow("default=").dimgray + Rainbow("#{column.default} ").skyblue if column.default
54
+ print "- #{Rainbow(column.comment).dimgray}" if column.comment
55
+ puts
56
+ end
57
+
58
+ def primary_key_column?(column)
59
+ column.name == primary_key
60
+ end
61
+
62
+ def foreign_key_column?(column)
63
+ return false if primary_key_column?(column)
64
+ column.name.end_with? "_id"
65
+ end
66
+
67
+ def timestamp_column?(column)
68
+ column.type == :datetime && column.name =~ /(created|updated|modified)/
69
+ end
70
+
71
+ def required_column?(column)
72
+ return false if primary_key_column?(column)
73
+ return false if foreign_key_column?(column)
74
+ return false if timestamp_column?(column)
75
+ !column.null
76
+ end
77
+
78
+ def limit_column?(column)
79
+ return false if primary_key_column?(column)
80
+ return false if foreign_key_column?(column)
81
+ return false if timestamp_column?(column)
82
+ %i[text string].include?(column.type) && column.limit.to_i > 0
83
+ end
84
+
85
+ def relation_column?(column)
86
+ return false if column.name == primary_key
87
+ column.name.end_with?("_id")
88
+ end
89
+
90
+ def fixture_columns
91
+ columns.sort_by(&:name).select do |column|
92
+ !primary_key_column?(column) && !timestamp_column?(column)
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelProbe::Probes::Indexes
4
+ def probe_indexes
5
+ indexes = connection.indexes(table_name)
6
+ name_pad = indexes.map { |c| c.name.length }.max + 1
7
+ indexes.sort_by(&:name).each do |index|
8
+ probe_index index, name_pad: name_pad
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def probe_index(index, name_pad:)
15
+ print Rainbow(index.name.to_s.rjust(name_pad)).yellow
16
+ print Rainbow(" [").dimgray
17
+ index.columns.each_with_index do |column, index|
18
+ print Rainbow(", ").dimgray if index > 0
19
+ print Rainbow(column).magenta
20
+ end
21
+ print Rainbow("]").dimgray
22
+ print Rainbow(" UNIQUE").green.bright if index.unique
23
+
24
+ if index.where
25
+ print Rainbow(" where(#{index.where})").dimgray
26
+ end
27
+
28
+ if index.using
29
+ print Rainbow(" using(#{index.using})").dimgray
30
+ end
31
+ puts
32
+ end
33
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelProbe::Probes::Metadata
4
+ def probe_metadata
5
+ puts Rainbow(name).green + Rainbow(" < ").dimgray.faint + Rainbow(superclass.name).green.faint
6
+ puts Rainbow(" Database engine ".ljust(24, ".")).darkgray + " " + Rainbow(connection.adapter_name).skyblue.bright + " " + Rainbow(connection.database_version).skyblue.faint
7
+ puts Rainbow(" Database name ".ljust(24, ".")).darkgray + " " + Rainbow(connection_db_config.database).skyblue
8
+ puts Rainbow(" Table name ".ljust(24, ".")).darkgray + " " + Rainbow(table_name).skyblue
9
+ puts Rainbow(" Default role".ljust(24, ".")).darkgray + " " + Rainbow(default_role).skyblue
10
+ puts Rainbow(" Connection config ".ljust(24, ".")).darkgray
11
+ connection_db_config.configuration_hash.to_yaml.split("\n")[1..].each { |line| puts " " + Rainbow(line).skyblue.faint }
12
+ puts
13
+ puts Rainbow(" DDL ".ljust(24, ".")).darkgray
14
+ if /create table/i.match?(ddl)
15
+ ddl.split("\n").each do |line|
16
+ line = line.squish
17
+ next if line.blank?
18
+ next if line.start_with?("--")
19
+ next if line.start_with?("/*")
20
+ next if /error|warning|Couldn't execute/i.match?(line)
21
+ puts Rainbow(" #{line}").skyblue.faint
22
+ end
23
+ else
24
+ puts Rainbow(" Failed to generate DDL string! #{ddl}").indianred
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def ddl
31
+ config = connection_db_config.configuration_hash
32
+ @ddl ||= begin
33
+ case connection.adapter_name
34
+ when /sqlite/i
35
+ `sqlite3 #{config[:database]} '.schema #{table_name}' 2>&1`
36
+ when /postgresql/i
37
+ `PGPASSWORD=#{config[:password]} pg_dump --host=#{config[:host]} --port=#{config[:port]} --username=#{config[:username]} --schema-only --table=#{table_name} #{config[:database]} 2>&1`
38
+ when /mysql/i
39
+ `mysqldump --host=#{config[:host]} --port=#{config[:port]} --user=#{config[:username]} --password=#{config[:password]} --skip-lock-tables --no-data --compact #{config[:database]} #{table_name} 2>&1`
40
+ else
41
+ "DDL output is not yet supported for #{connection.adapter_name}!"
42
+ end
43
+ rescue => e
44
+ "Failed to generate DDL string! #{e.message}"
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelProbe::Probes::Subclasses
4
+ def probe_subclasses
5
+ @count = 0
6
+ @max_name_size = 0
7
+ puts Rainbow(name).green + Rainbow(" < ").dimgray.faint + Rainbow(superclass.name).green.faint
8
+ grouped = subclasses.each_with_object({}) do |model, memo|
9
+ @max_name_size = model.name.size if model.name.size > @max_name_size
10
+ location = Object.const_source_location(model.name)
11
+ case location
12
+ when Array
13
+ path = location.first
14
+ type = "First party" if first_party?(path)
15
+ type = "Third party" if third_party?(path)
16
+ type ||= "Unknown"
17
+ memo[type] ||= []
18
+ memo[type] << {name: model.name, path: path}
19
+ when NilClass
20
+ memo["Dynamic"] ||= []
21
+ memo["Dynamic"] << {name: model.name, path: "defined dynamically at runtime"}
22
+ else
23
+ memo["Unknown"] ||= []
24
+ memo["Unknown"] << {name: model.name, path: nil}
25
+ end
26
+ end
27
+
28
+ keys = [
29
+ "First party",
30
+ "Dynamic",
31
+ "Third party",
32
+ "Unknown"
33
+ ]
34
+
35
+ keys.each do |key|
36
+ next unless grouped[key]&.any?
37
+ puts Rainbow(" #{key} subclasses ".ljust(35, ".")).dimgray
38
+ grouped[key].sort_by { |entry| entry[:name] }.each do |entry|
39
+ probe_subclass entry[:name], entry[:path]
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def probe_subclass(name, path)
47
+ path = path.split(/\/(?=app\/models\/)/i).last if path.include?("/app/models/") && !path.include?("/ruby/gems/")
48
+ path = path.split(/\/(?=ruby\/gems\/)/i).last if path.include?("/ruby/gems/")
49
+ prefix = (@count += 1).to_s.rjust(8) + " ..."
50
+ puts [
51
+ Rainbow(prefix.to_s.ljust(prefix.size + @max_name_size - name.size, ".")).dimgray.faint,
52
+ Rainbow(name).mediumslateblue,
53
+ Rainbow("<").faint,
54
+ Rainbow(self.name).dimgray.faint,
55
+ Rainbow(path.to_s).faint
56
+ ].join(" ")
57
+ end
58
+
59
+ def third_party?(path)
60
+ path.include? "/ruby/gems/"
61
+ end
62
+
63
+ def first_party?(path)
64
+ return false if third_party?(path)
65
+ path.include? "/app/models/"
66
+ end
67
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ModelProbe::Probes; end
4
+
5
+ require_relative "probes/metadata"
6
+ require_relative "probes/columns"
7
+ require_relative "probes/indexes"
8
+ require_relative "probes/subclasses"
9
+
10
+ module ModelProbe::Probes
11
+ include Metadata
12
+ include Columns
13
+ include Indexes
14
+ include Subclasses
15
+
16
+ def probe
17
+ if name == "ActiveRecord::Base" || abstract_class?
18
+ probe_subclasses
19
+ return nil
20
+ end
21
+
22
+ probe_metadata
23
+ puts
24
+
25
+ puts Rainbow(" Columns ".ljust(24, ".")).darkgray
26
+ probe_columns
27
+ puts
28
+
29
+ puts Rainbow(" Indexes ".ljust(24, ".")).darkgray
30
+ probe_indexes
31
+ puts
32
+
33
+ nil
34
+ end
35
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  if Rails.env.development?
2
4
  ActiveRecord::Base.extend ModelProbe
3
5
 
@@ -0,0 +1,4 @@
1
+ <%= name.underscore %>_fixture:
2
+ <% fixture_columns.each do |column| -%>
3
+ <%= column.name.underscore %>: <%= column&.default || column.type %>
4
+ <% end -%>
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= name -%> < <%= superclass_name %>
4
+ # extends ..................................................................................................
5
+
6
+ # includes .................................................................................................
7
+
8
+ # constants ................................................................................................
9
+
10
+ # class methods ............................................................................................
11
+ class << self
12
+ end
13
+
14
+ # relationships ............................................................................................
15
+ <% foreign_key_columns.each do |column| -%>
16
+ <%= "belongs_to :#{column.name.sub(/_id\z/, "")}" %>
17
+ <% end -%>
18
+
19
+ # validations ..............................................................................................
20
+ <% validation_columns.each do |column| -%>
21
+ <%= "validates :#{column.name}, presence: true" unless column.null if required_columns.include?(column) %>
22
+ <%= "validates :#{column.name}, length: {maximum: #{column.limit}}" if limit_columns.include?(column) %>
23
+ <% end -%>
24
+
25
+ # callbacks (caution: side effects) ........................................................................
26
+
27
+ # scopes (composable queries) ..............................................................................
28
+
29
+ # additional config (accepts_nested_attribute_for, etc.) ...................................................
30
+
31
+ # public instance methods ..................................................................................
32
+
33
+ # protected instance methods ...............................................................................
34
+
35
+ # private instance methods .................................................................................
36
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ModelProbe
2
- VERSION = "1.0.7"
4
+ VERSION = "1.1.1"
3
5
  end
data/lib/model_probe.rb CHANGED
@@ -1,114 +1,43 @@
1
- require "model_probe/version"
2
- require "model_probe/color"
3
- require "model_probe/railtie" if defined?(Rails)
1
+ # frozen_string_literal: true
4
2
 
5
- module ModelProbe
6
- include ModelProbe::Color
7
-
8
- # Pretty prints column meta data for an ActiveModel
9
- def probe
10
- name_pad = columns.map { |c| c.name.length }.max + 1
11
- type_pad = columns.map { |c| c.type.length }.max + 2
12
- sql_type_pad = columns.map { |c| c.sql_type.length }.max + 1
3
+ require "erb"
4
+ require "rainbow"
5
+ require_relative "model_probe/version"
6
+ require_relative "model_probe/railtie" if defined?(Rails)
7
+ require_relative "model_probe/probes"
13
8
 
14
- columns.sort { |a, b| a.name <=> b.name }.map do |column|
15
- name = column.name
16
- name = "* #{name}" if primary_key_column?(column)
17
- print yellow(name.to_s.rjust(name_pad))
18
- print " "
19
- print blue(column.type.to_s.ljust(type_pad, "."))
20
- print magenta(column.sql_type.to_s.ljust(sql_type_pad))
21
- column.null ? print(red("NULL")) : print(" ")
22
- print " [#{column.default}]" if column.default
23
- print " #{gray column.comment}" if column.comment
24
- puts
25
- end
26
- nil
27
- end
9
+ module ModelProbe
10
+ include Probes
28
11
 
29
12
  # Prints a stub that can be used for a test fixture
30
13
  def print_fixture
31
- values = columns.sort_by(&:name).each_with_object({}) do |column, memo|
32
- next if primary_key_column?(column)
33
- next if timestamp_column?(column)
34
- memo[column.name] = column.default || "value"
35
- end
36
-
37
- hash = { self.name.underscore => values }
38
- puts hash.to_yaml
14
+ template = erb_template("model_probe/templates/fixture.yml.erb")
15
+ puts template.result_with_hash(name: name, fixture_columns: fixture_columns)
39
16
  nil
40
17
  end
41
18
 
42
19
  # Prints a new model definition based on the database schema
43
20
  def print_model
44
- puts "class #{name} < #{superclass.name}"
45
- puts " # extends ..................................................................."
46
- puts " # includes .................................................................."
47
- puts if relation_columns.size > 0
48
- puts " # relationships ............................................................."
49
- relation_columns.sort_by(&:name).each do |column|
50
- next if primary_key_column?(column)
51
- puts " belongs_to :#{column.name.sub(/_id\z/, "")}" if column.name =~ /_id\z/
52
- end
53
- puts if relation_columns.size > 0 || validation_columns.size > 0
54
- puts " # validations ..............................................................."
55
- validation_columns.sort_by(&:name).each do |column|
56
- next if primary_key_column?(column)
57
- puts " validates :#{column.name}, presence: true" unless column.null
58
- if %i(text string).include?(column.type) && column.limit.to_i > 0
59
- puts " validates :#{column.name}, length: { maximum: #{column.limit} }"
60
- end
61
- end
62
- puts if validation_columns.size > 0
63
- puts " # callbacks ................................................................."
64
- puts " # scopes ...................................................................."
65
- puts " # additional config (i.e. accepts_nested_attribute_for etc...) .............."
66
- puts
67
- puts " # class methods ............................................................."
68
- puts " class << self"
69
- puts " end"
70
- puts
71
- puts " # public instance methods ..................................................."
72
- puts
73
- puts " # protected instance methods ................................................"
74
- puts " protected"
75
- puts
76
- puts " # private instance methods .................................................."
77
- puts " private"
78
- puts "end"
21
+ template = erb_template("model_probe/templates/model.rb.erb")
22
+ puts template.result_with_hash(
23
+ name: name,
24
+ superclass_name: superclass.name,
25
+ primary_key_columns: primary_key_columns,
26
+ foreign_key_columns: foreign_key_columns,
27
+ relation_columns: relation_columns,
28
+ required_columns: required_columns,
29
+ limit_columns: limit_columns,
30
+ validation_columns: validation_columns
31
+ )
79
32
  nil
80
33
  end
81
34
 
82
35
  private
83
36
 
84
- def relation_columns
85
- @relation_columns ||= begin
86
- columns.select { |column| relation_column? column }
87
- end
88
- end
89
-
90
- def validation_columns
91
- @validation_columns ||= begin
92
- columns.select { |column| validation_column? column }
93
- end
94
- end
95
-
96
- def primary_key_column?(column)
97
- column.name == primary_key
98
- end
99
-
100
- def timestamp_column?(column)
101
- column.type == :datetime && column.name =~ /(created|updated|modified)/
102
- end
103
-
104
- def relation_column?(column)
105
- return false if column.name == primary_key
106
- column.name.end_with?("_id")
107
- end
108
-
109
- def validation_column?(column)
110
- return false if column.name == primary_key
111
- return true unless column.null
112
- %i(text string).include?(column.type) && column.limit.to_i > 0
113
- end
37
+ def erb_template(relative_path)
38
+ template_path = File.expand_path(relative_path, __dir__)
39
+ template_text = File.read(template_path)
40
+ # trim_mode doesn't seem to work regardless of how it's passed with Ruby 2.7.5
41
+ ERB.new template_text, trim_mode: "-"
42
+ end
114
43
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  namespace :model_probe do
2
4
  desc <<~DESC
3
5
  Probe. Usage: `rails model:probe[User]`
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/model_probe/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "model_probe"
7
+ s.version = ModelProbe::VERSION
8
+ s.authors = ["Nathan Hopkins"]
9
+ s.email = ["natehop@gmail.com"]
10
+ s.summary = "ActiveRecord schema visualization and model organization made easy"
11
+ s.homepage = "http://hopsoft.github.com/model_probe/"
12
+
13
+ s.files = Dir["lib/**/*rb", "lib/tasks/*rake", "[A-Z]*"]
14
+
15
+ s.add_dependency "rainbow"
16
+
17
+ s.add_development_dependency "magic_frozen_string_literal"
18
+ s.add_development_dependency "standardrb"
19
+ end
data/tags ADDED
@@ -0,0 +1,111 @@
1
+ !_TAG_EXTRA_DESCRIPTION anonymous /Include tags for non-named objects like lambda/
2
+ !_TAG_EXTRA_DESCRIPTION fileScope /Include tags of file scope/
3
+ !_TAG_EXTRA_DESCRIPTION pseudo /Include pseudo tags/
4
+ !_TAG_EXTRA_DESCRIPTION subparser /Include tags generated by subparsers/
5
+ !_TAG_FIELD_DESCRIPTION epoch /the last modified time of the input file (only for F\/file kind tag)/
6
+ !_TAG_FIELD_DESCRIPTION file /File-restricted scoping/
7
+ !_TAG_FIELD_DESCRIPTION input /input file/
8
+ !_TAG_FIELD_DESCRIPTION name /tag name/
9
+ !_TAG_FIELD_DESCRIPTION pattern /pattern/
10
+ !_TAG_FIELD_DESCRIPTION typeref /Type and name of a variable or typedef/
11
+ !_TAG_FIELD_DESCRIPTION!Ruby mixin /how the class or module is mixed in (mixin:HOW:MODULE)/
12
+ !_TAG_FILE_FORMAT 2 /extended format; --format=1 will not append ;" to lines/
13
+ !_TAG_FILE_SORTED 1 /0=unsorted, 1=sorted, 2=foldcase/
14
+ !_TAG_KIND_DESCRIPTION!GemSpec g,gem /gems/
15
+ !_TAG_KIND_DESCRIPTION!Markdown S,subsection /level 2 sections/
16
+ !_TAG_KIND_DESCRIPTION!Markdown T,l4subsection /level 4 sections/
17
+ !_TAG_KIND_DESCRIPTION!Markdown c,chapter /chapters/
18
+ !_TAG_KIND_DESCRIPTION!Markdown n,footnote /footnotes/
19
+ !_TAG_KIND_DESCRIPTION!Markdown s,section /sections/
20
+ !_TAG_KIND_DESCRIPTION!Markdown t,subsubsection /level 3 sections/
21
+ !_TAG_KIND_DESCRIPTION!Markdown u,l5subsection /level 5 sections/
22
+ !_TAG_KIND_DESCRIPTION!Rake d,directory /directory tasks/
23
+ !_TAG_KIND_DESCRIPTION!Rake f,File /file tasks/
24
+ !_TAG_KIND_DESCRIPTION!Rake m,multitask /multi tasks/
25
+ !_TAG_KIND_DESCRIPTION!Rake n,namespace /namespaces/
26
+ !_TAG_KIND_DESCRIPTION!Rake t,task /tasks/
27
+ !_TAG_KIND_DESCRIPTION!Rake x,xtask /tasks defined with special constructor/
28
+ !_TAG_KIND_DESCRIPTION!Ruby A,accessor /accessors/
29
+ !_TAG_KIND_DESCRIPTION!Ruby C,constant /constants/
30
+ !_TAG_KIND_DESCRIPTION!Ruby L,library /libraries/
31
+ !_TAG_KIND_DESCRIPTION!Ruby S,singletonMethod /singleton methods/
32
+ !_TAG_KIND_DESCRIPTION!Ruby a,alias /aliases/
33
+ !_TAG_KIND_DESCRIPTION!Ruby c,class /classes/
34
+ !_TAG_KIND_DESCRIPTION!Ruby f,method /methods/
35
+ !_TAG_KIND_DESCRIPTION!Ruby m,module /modules/
36
+ !_TAG_KIND_DESCRIPTION!Sh a,alias /aliases/
37
+ !_TAG_KIND_DESCRIPTION!Sh f,function /functions/
38
+ !_TAG_KIND_DESCRIPTION!Sh h,heredoc /label for here document/
39
+ !_TAG_KIND_DESCRIPTION!Sh s,script /script files/
40
+ !_TAG_OUTPUT_EXCMD mixed /number, pattern, mixed, or combineV2/
41
+ !_TAG_OUTPUT_FILESEP slash /slash or backslash/
42
+ !_TAG_OUTPUT_MODE u-ctags /u-ctags or e-ctags/
43
+ !_TAG_OUTPUT_VERSION 0.0 /current.age/
44
+ !_TAG_PARSER_VERSION!GemSpec 0.0 /current.age/
45
+ !_TAG_PARSER_VERSION!Markdown 0.0 /current.age/
46
+ !_TAG_PARSER_VERSION!Rake 0.0 /current.age/
47
+ !_TAG_PARSER_VERSION!Ruby 0.0 /current.age/
48
+ !_TAG_PARSER_VERSION!Sh 0.0 /current.age/
49
+ !_TAG_PATTERN_LENGTH_LIMIT 96 /0 for no limit/
50
+ !_TAG_PROC_CWD /Users/nate.hopkins/work/hopsoft/model_probe/ //
51
+ !_TAG_PROGRAM_AUTHOR Universal Ctags Team //
52
+ !_TAG_PROGRAM_NAME Universal Ctags /Derived from Exuberant Ctags/
53
+ !_TAG_PROGRAM_URL https://ctags.io/ /official site/
54
+ !_TAG_PROGRAM_VERSION 6.0.0 //
55
+ !_TAG_ROLE_DESCRIPTION!GemSpec!gem develDep /specifying development dependency/
56
+ !_TAG_ROLE_DESCRIPTION!GemSpec!gem runtimeDep /specifying runtime dependency/
57
+ !_TAG_ROLE_DESCRIPTION!Ruby!library loaded /loaded by "load" method/
58
+ !_TAG_ROLE_DESCRIPTION!Ruby!library required /loaded by "require" method/
59
+ !_TAG_ROLE_DESCRIPTION!Ruby!library requiredRel /loaded by "require_relative" method/
60
+ !_TAG_ROLE_DESCRIPTION!Sh!heredoc endmarker /end marker/
61
+ !_TAG_ROLE_DESCRIPTION!Sh!script loaded /loaded/
62
+ ActiveRecord schema visualization and model organization made easy 🙌 README.md /^## ActiveRecord schema visualization and model organization made easy 🙌$/;" s chapter:ModelProbe
63
+ Columns lib/model_probe/probes/columns.rb /^module ModelProbe::Probes::Columns$/;" m module:ModelProbe.Probes
64
+ Indexes lib/model_probe/probes/indexes.rb /^module ModelProbe::Probes::Indexes$/;" m module:ModelProbe.Probes
65
+ Metadata lib/model_probe/probes/metadata.rb /^module ModelProbe::Probes::Metadata$/;" m module:ModelProbe.Probes
66
+ ModelProbe README.md /^# ModelProbe$/;" c
67
+ ModelProbe lib/model_probe.rb /^module ModelProbe$/;" m mixin:include:Probes
68
+ ModelProbe lib/model_probe/railtie.rb /^ module ModelProbe$/;" m
69
+ ModelProbe lib/model_probe/version.rb /^module ModelProbe$/;" m
70
+ Probes lib/model_probe/probes.rb /^module ModelProbe::Probes$/;" m module:ModelProbe mixin:include:Metadata,include:Columns,include:Indexes,include:Subclasses
71
+ Probes lib/model_probe/probes.rb /^module ModelProbe::Probes; end$/;" m module:ModelProbe
72
+ Quick Start README.md /^## Quick Start$/;" s chapter:ModelProbe
73
+ Railtie lib/model_probe/railtie.rb /^ class Railtie < Rails::Railtie$/;" c module:ModelProbe
74
+ Screenshots README.md /^## Screenshots$/;" s chapter:ModelProbe
75
+ Subclasses lib/model_probe/probes/subclasses.rb /^module ModelProbe::Probes::Subclasses$/;" m module:ModelProbe.Probes
76
+ Supported Databases README.md /^## Supported Databases$/;" s chapter:ModelProbe
77
+ Table of Contents README.md /^## Table of Contents$/;" s chapter:ModelProbe
78
+ VERSION lib/model_probe/version.rb /^ VERSION = "1.1.1"$/;" C module:ModelProbe
79
+ Videos README.md /^## Videos$/;" s chapter:ModelProbe
80
+ ddl lib/model_probe/probes/metadata.rb /^ def ddl$/;" f module:Metadata
81
+ erb_template lib/model_probe.rb /^ def erb_template(relative_path)$/;" f module:ModelProbe
82
+ first_party? lib/model_probe/probes/subclasses.rb /^ def first_party?(path)$/;" f module:Subclasses
83
+ fixture_columns lib/model_probe/probes/columns.rb /^ def fixture_columns$/;" f module:Columns
84
+ foreign_key_column? lib/model_probe/probes/columns.rb /^ def foreign_key_column?(column)$/;" f module:Columns
85
+ foreign_key_columns lib/model_probe/probes/columns.rb /^ def foreign_key_columns$/;" f module:Columns
86
+ limit_column? lib/model_probe/probes/columns.rb /^ def limit_column?(column)$/;" f module:Columns
87
+ limit_columns lib/model_probe/probes/columns.rb /^ def limit_columns$/;" f module:Columns
88
+ model_probe lib/tasks/model_probe.rake /^namespace :model_probe do$/;" n
89
+ model_probe model_probe.gemspec /^ s.name = "model_probe"$/;" g
90
+ primary_key_column? lib/model_probe/probes/columns.rb /^ def primary_key_column?(column)$/;" f module:Columns
91
+ primary_key_columns lib/model_probe/probes/columns.rb /^ def primary_key_columns$/;" f module:Columns
92
+ print_fixture lib/model_probe.rb /^ def print_fixture$/;" f module:ModelProbe
93
+ print_fixture lib/tasks/model_probe.rake /^ task :print_fixture, [:klass] => :environment do |task, args|$/;" t namespace:model_probe
94
+ print_model lib/model_probe.rb /^ def print_model$/;" f module:ModelProbe
95
+ print_model lib/tasks/model_probe.rake /^ task :print_model, [:klass] => :environment do |task, args|$/;" t namespace:model_probe
96
+ probe lib/model_probe/probes.rb /^ def probe$/;" f module:Probes
97
+ probe lib/tasks/model_probe.rake /^ task :probe, [:klass] => :environment do |task, args|$/;" t namespace:model_probe
98
+ probe_column lib/model_probe/probes/columns.rb /^ def probe_column(column, name_pad:, type_pad:, sql_type_pad:)$/;" f module:Columns
99
+ probe_columns lib/model_probe/probes/columns.rb /^ def probe_columns$/;" f module:Columns
100
+ probe_index lib/model_probe/probes/indexes.rb /^ def probe_index(index, name_pad:)$/;" f module:Indexes
101
+ probe_indexes lib/model_probe/probes/indexes.rb /^ def probe_indexes$/;" f module:Indexes
102
+ probe_metadata lib/model_probe/probes/metadata.rb /^ def probe_metadata$/;" f module:Metadata
103
+ probe_subclass lib/model_probe/probes/subclasses.rb /^ def probe_subclass(name, path)$/;" f module:Subclasses
104
+ probe_subclasses lib/model_probe/probes/subclasses.rb /^ def probe_subclasses$/;" f module:Subclasses
105
+ relation_column? lib/model_probe/probes/columns.rb /^ def relation_column?(column)$/;" f module:Columns
106
+ relation_columns lib/model_probe/probes/columns.rb /^ def relation_columns$/;" f module:Columns
107
+ required_column? lib/model_probe/probes/columns.rb /^ def required_column?(column)$/;" f module:Columns
108
+ required_columns lib/model_probe/probes/columns.rb /^ def required_columns$/;" f module:Columns
109
+ third_party? lib/model_probe/probes/subclasses.rb /^ def third_party?(path)$/;" f module:Subclasses
110
+ timestamp_column? lib/model_probe/probes/columns.rb /^ def timestamp_column?(column)$/;" f module:Columns
111
+ validation_columns lib/model_probe/probes/columns.rb /^ def validation_columns$/;" f module:Columns
metadata CHANGED
@@ -1,17 +1,45 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_probe
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan Hopkins
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-11-12 00:00:00.000000000 Z
11
+ date: 2023-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rake
14
+ name: rainbow
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: magic_frozen_string_literal
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: standardrb
15
43
  requirement: !ruby/object:Gem::Requirement
16
44
  requirements:
17
45
  - - ">="
@@ -37,10 +65,18 @@ files:
37
65
  - README.md
38
66
  - Rakefile
39
67
  - lib/model_probe.rb
40
- - lib/model_probe/color.rb
68
+ - lib/model_probe/probes.rb
69
+ - lib/model_probe/probes/columns.rb
70
+ - lib/model_probe/probes/indexes.rb
71
+ - lib/model_probe/probes/metadata.rb
72
+ - lib/model_probe/probes/subclasses.rb
41
73
  - lib/model_probe/railtie.rb
74
+ - lib/model_probe/templates/fixture.yml.erb
75
+ - lib/model_probe/templates/model.rb.erb
42
76
  - lib/model_probe/version.rb
43
77
  - lib/tasks/model_probe.rake
78
+ - model_probe.gemspec
79
+ - tags
44
80
  homepage: http://hopsoft.github.com/model_probe/
45
81
  licenses: []
46
82
  metadata: {}
@@ -59,9 +95,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
95
  - !ruby/object:Gem::Version
60
96
  version: '0'
61
97
  requirements: []
62
- rubyforge_project:
63
- rubygems_version: 2.6.13
98
+ rubygems_version: 3.1.6
64
99
  signing_key:
65
100
  specification_version: 4
66
- summary: Schema introspection for ActiveModel
101
+ summary: ActiveRecord schema visualization and model organization made easy
67
102
  test_files: []
@@ -1,22 +0,0 @@
1
- module ModelProbe
2
- module Color
3
- extend self
4
-
5
- colors = {
6
- :gray => "1;30",
7
- :red => 31,
8
- :green => 32,
9
- :yellow => 33,
10
- :blue => 34,
11
- :magenta => 35,
12
- :cyan => 36,
13
- :white => 37
14
- }
15
-
16
- colors.each do |name, code|
17
- define_method name do |text|
18
- "\e[#{code}m#{text}\e[0m"
19
- end
20
- end
21
- end
22
- end