crudboy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,90 @@
1
+ require 'optparse'
2
+ require 'ostruct'
3
+
4
+ module Crudboy
5
+ class Cli
6
+ class << self
7
+ def start
8
+ parse_options!
9
+ App.new(@options).run!
10
+ end
11
+
12
+ def config_template_bundle(template_bundle)
13
+ return template_bundle if File.exist?(template_bundle)
14
+ File.join(File.expand_path("~/.crudboy.d"), 'bundles', template_bundle)
15
+ end
16
+
17
+ def parse_options!
18
+ @options = OpenStruct.new(config_file: default_config_file,
19
+ initializer: default_initializer,
20
+ destination: Dir.pwd,
21
+ ssh: {})
22
+
23
+
24
+ OptionParser.new do |opts|
25
+ opts.banner = <<~EOF
26
+ Usage: crudboy [options] [ruby file]
27
+
28
+ If neither [ruby file] nor -e option specified, and STDIN is not a tty, a Pry REPL will be launched,
29
+ otherwise the specified ruby file or -e option value or ruby code read from STDIN will be run, and no REPL launched
30
+
31
+ EOF
32
+
33
+ opts.on('-cCONFIG_FILE', '--conf=CONFIG_FILE', 'Specify config file, default is $HOME/.crudboy.yml, or $HOME/.crudboy.d/init.yml.') do |config_file|
34
+ @options.config_file = config_file
35
+ end
36
+
37
+ opts.on('-iINITIALIZER', '--initializer=INITIALIZER', 'Specify initializer ruby file, default is $HOME/.crudboy.rb, or $HOME/.crudboy.d/init.rb.') do |initializer|
38
+ @options.initializer = initializer
39
+ end
40
+
41
+ opts.on('-eENVIRON', '--env=ENVIRON', 'Specify config environment.') do |env|
42
+ @options.env = env
43
+ end
44
+
45
+ opts.on('-tTABLE_NAME', '--table=TABLE_NAME', 'Specify table name') do |table_name|
46
+ @options.table_name = table_name
47
+ end
48
+
49
+ opts.on('-mMODEL_NAME', '--model=MODEL_NAME', 'Specify model name') do |model_name|
50
+ @options.model_name = model_name
51
+ end
52
+
53
+ opts.on('-oOUTPUT', '--output=OUTPUT', 'Specify output path, default: $PWD') do |destination|
54
+ @options.destination = destination
55
+ end
56
+
57
+ opts.on('-bTEMPLATE_BUNDLE', '--bundle=TEMPLATE_BUNDLE', 'Specify template bundle, may be a path point to a .crudboy file or a directory') do |template_bundle|
58
+ @options.template_bundle = config_template_bundle(template_bundle)
59
+ end
60
+
61
+ opts.on('', '--help', 'Prints this help') do
62
+ puts opts
63
+ exit
64
+ end
65
+
66
+ end.parse!
67
+
68
+ @options.template_args = ARGV
69
+ end
70
+
71
+ def default_config_file
72
+ conf = File.expand_path('~/.crudboy.yml')
73
+ return conf if File.file?(conf)
74
+ conf = File.expand_path('~/.crudboy.yaml')
75
+ return conf if File.file?(conf)
76
+ conf = File.expand_path('~/.crudboy.d/init.yml')
77
+ return conf if File.file?(conf)
78
+ conf = File.expand_path('~/.crudboy.d/init.yaml')
79
+ return conf if File.file?(conf)
80
+ end
81
+
82
+ def default_initializer
83
+ conf = File.expand_path('~/.crudboy.rb')
84
+ return conf if File.file?(conf)
85
+ conf = File.expand_path('~/.crudboy.d/init.rb')
86
+ return conf if File.file?(conf)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,86 @@
1
+ module Crudboy
2
+ class Column
3
+
4
+ JAVA_TYPES = {
5
+ "varchar" => 'String',
6
+ "char" => 'String',
7
+ "int" => 'Integer',
8
+ "bigint" => 'Long',
9
+ "tinyint" => 'Byte',
10
+ "date" => 'LocalDate',
11
+ "datetime" => 'LocalDateTime',
12
+ "timestamp" => 'LocalDateTime',
13
+ "decimal" => 'BigDecimal'
14
+ }
15
+
16
+ JDBC_TYPES = {
17
+ "varchar" => 'VARCHAR',
18
+ "char" => 'CHAR',
19
+ "int" => 'INTEGER',
20
+ "bigint" => 'BIGINT',
21
+ "tinyint" => 'TINYINT',
22
+ "date" => 'TIMESTAMP',
23
+ "datetime" => 'TIMESTAMP',
24
+ "timestamp" => 'TIMESTAMP',
25
+ "decimal" => 'DECIMAL'
26
+ }
27
+
28
+ attr_accessor :active_record_column, :primary
29
+
30
+ def initialize(column, primary)
31
+ @active_record_column = column
32
+ @primary = primary
33
+ end
34
+
35
+ def java_doc
36
+ <<-EOF.lstrip.chomp
37
+ /**
38
+ * #{comment}
39
+ */
40
+ EOF
41
+ end
42
+
43
+ def mybatis_value_expression
44
+ format('#{%s,jdbcType=%s}', lower_camel_name, jdbc_type)
45
+ end
46
+
47
+ def mybatis_equation
48
+ format('`%s` = %s', name, mybatis_value_expression)
49
+ end
50
+
51
+ def mybatis_result_map
52
+ if @primary
53
+ format('<id column="%s" jdbcType="%s" property="%s" />', name, jdbc_type, lower_camel_name)
54
+ else
55
+ format('<result column="%s" jdbcType="%s" property="%s" />', name, jdbc_type, lower_camel_name)
56
+ end
57
+ end
58
+
59
+ def lower_camel_name
60
+ name.camelcase(:lower)
61
+ end
62
+
63
+ def upper_camel_name
64
+ name.camelcase(:upper)
65
+ end
66
+
67
+ def java_type
68
+ return 'Boolean' if sql_type == 'tinyint(1)'
69
+ raw_type = sql_type.scan(/^\w+/).first
70
+ JAVA_TYPES[raw_type]
71
+ end
72
+
73
+ def jdbc_type
74
+ raw_type = sql_type.scan(/^\w+/).first
75
+ JDBC_TYPES[raw_type]
76
+ end
77
+
78
+ def method_missing(method, *args, **options, &block)
79
+ if active_record_column.respond_to?(method)
80
+ active_record_column.send(method, *args, **options, &block)
81
+ else
82
+ super
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,2 @@
1
+ require 'crudboy/concerns/global_data_definition'
2
+ require 'crudboy/concerns/table_data_definition'
@@ -0,0 +1,244 @@
1
+ require 'active_support/concern'
2
+
3
+ module Crudboy
4
+ module Concerns
5
+ module GlobalDataDefinition
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+
10
+ # Example:
11
+ #
12
+ # create_table :post, id: false, primary_key: :id do |t|
13
+ # t.column :id, :bigint, precison: 19, comment: 'ID'
14
+ # t.column :name, :string, comment: '名称'
15
+ # t.column :gmt_created, :datetime, comment: '创建时间'
16
+ # t.column :gmt_modified, :datetime, comment: '最后修改时间'
17
+ # end
18
+ #
19
+ # Creates a new table with the name +table_name+. +table_name+ may either
20
+ # be a String or a Symbol.
21
+ #
22
+ # There are two ways to work with #create_table. You can use the block
23
+ # form or the regular form, like this:
24
+ #
25
+ # === Block form
26
+ #
27
+ # # create_table() passes a TableDefinition object to the block.
28
+ # # This form will not only create the table, but also columns for the
29
+ # # table.
30
+ #
31
+ # create_table(:suppliers) do |t|
32
+ # t.column :name, :string, limit: 60
33
+ # # Other fields here
34
+ # end
35
+ #
36
+ # === Block form, with shorthand
37
+ #
38
+ # # You can also use the column types as method calls, rather than calling the column method.
39
+ # create_table(:suppliers) do |t|
40
+ # t.string :name, limit: 60
41
+ # # Other fields here
42
+ # end
43
+ #
44
+ # === Regular form
45
+ #
46
+ # # Creates a table called 'suppliers' with no columns.
47
+ # create_table(:suppliers)
48
+ # # Add a column to 'suppliers'.
49
+ # add_column(:suppliers, :name, :string, {limit: 60})
50
+ #
51
+ # The +options+ hash can include the following keys:
52
+ # [<tt>:id</tt>]
53
+ # Whether to automatically add a primary key column. Defaults to true.
54
+ # Join tables for {ActiveRecord::Base.has_and_belongs_to_many}[rdoc-ref:Associations::ClassMethods#has_and_belongs_to_many] should set it to false.
55
+ #
56
+ # A Symbol can be used to specify the type of the generated primary key column.
57
+ # [<tt>:primary_key</tt>]
58
+ # The name of the primary key, if one is to be added automatically.
59
+ # Defaults to +id+. If <tt>:id</tt> is false, then this option is ignored.
60
+ #
61
+ # If an array is passed, a composite primary key will be created.
62
+ #
63
+ # Note that Active Record models will automatically detect their
64
+ # primary key. This can be avoided by using
65
+ # {self.primary_key=}[rdoc-ref:AttributeMethods::PrimaryKey::ClassMethods#primary_key=] on the model
66
+ # to define the key explicitly.
67
+ #
68
+ # [<tt>:options</tt>]
69
+ # Any extra options you want appended to the table definition.
70
+ # [<tt>:temporary</tt>]
71
+ # Make a temporary table.
72
+ # [<tt>:force</tt>]
73
+ # Set to true to drop the table before creating it.
74
+ # Set to +:cascade+ to drop dependent objects as well.
75
+ # Defaults to false.
76
+ # [<tt>:if_not_exists</tt>]
77
+ # Set to true to avoid raising an error when the table already exists.
78
+ # Defaults to false.
79
+ # [<tt>:as</tt>]
80
+ # SQL to use to generate the table. When this option is used, the block is
81
+ # ignored, as are the <tt>:id</tt> and <tt>:primary_key</tt> options.
82
+ #
83
+ # ====== Add a backend specific option to the generated SQL (MySQL)
84
+ #
85
+ # create_table(:suppliers, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8mb4')
86
+ #
87
+ # generates:
88
+ #
89
+ # CREATE TABLE suppliers (
90
+ # id bigint auto_increment PRIMARY KEY
91
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
92
+ #
93
+ # ====== Rename the primary key column
94
+ #
95
+ # create_table(:objects, primary_key: 'guid') do |t|
96
+ # t.column :name, :string, limit: 80
97
+ # end
98
+ #
99
+ # generates:
100
+ #
101
+ # CREATE TABLE objects (
102
+ # guid bigint auto_increment PRIMARY KEY,
103
+ # name varchar(80)
104
+ # )
105
+ #
106
+ # ====== Change the primary key column type
107
+ #
108
+ # create_table(:tags, id: :string) do |t|
109
+ # t.column :label, :string
110
+ # end
111
+ #
112
+ # generates:
113
+ #
114
+ # CREATE TABLE tags (
115
+ # id varchar PRIMARY KEY,
116
+ # label varchar
117
+ # )
118
+ #
119
+ # ====== Create a composite primary key
120
+ #
121
+ # create_table(:orders, primary_key: [:product_id, :client_id]) do |t|
122
+ # t.belongs_to :product
123
+ # t.belongs_to :client
124
+ # end
125
+ #
126
+ # generates:
127
+ #
128
+ # CREATE TABLE order (
129
+ # product_id bigint NOT NULL,
130
+ # client_id bigint NOT NULL
131
+ # );
132
+ #
133
+ # ALTER TABLE ONLY "orders"
134
+ # ADD CONSTRAINT orders_pkey PRIMARY KEY (product_id, client_id);
135
+ #
136
+ # ====== Do not add a primary key column
137
+ #
138
+ # create_table(:categories_suppliers, id: false) do |t|
139
+ # t.column :category_id, :bigint
140
+ # t.column :supplier_id, :bigint
141
+ # end
142
+ #
143
+ # generates:
144
+ #
145
+ # CREATE TABLE categories_suppliers (
146
+ # category_id bigint,
147
+ # supplier_id bigint
148
+ # )
149
+ #
150
+ # ====== Create a temporary table based on a query
151
+ #
152
+ # create_table(:long_query, temporary: true,
153
+ # as: "SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id")
154
+ #
155
+ # generates:
156
+ #
157
+ # CREATE TEMPORARY TABLE long_query AS
158
+ # SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
159
+ #
160
+ # See also TableDefinition#column for details on how to create columns.
161
+ def create_table(table_name, **options, &blk)
162
+ ActiveRecord::Base.connection.create_table(table_name, **options, &blk)
163
+ end
164
+
165
+ # Creates a new join table with the name created using the lexical order of the first two
166
+ # arguments. These arguments can be a String or a Symbol.
167
+ #
168
+ # # Creates a table called 'assemblies_parts' with no id.
169
+ # create_join_table(:assemblies, :parts)
170
+ #
171
+ # You can pass an +options+ hash which can include the following keys:
172
+ # [<tt>:table_name</tt>]
173
+ # Sets the table name, overriding the default.
174
+ # [<tt>:column_options</tt>]
175
+ # Any extra options you want appended to the columns definition.
176
+ # [<tt>:options</tt>]
177
+ # Any extra options you want appended to the table definition.
178
+ # [<tt>:temporary</tt>]
179
+ # Make a temporary table.
180
+ # [<tt>:force</tt>]
181
+ # Set to true to drop the table before creating it.
182
+ # Defaults to false.
183
+ #
184
+ # Note that #create_join_table does not create any indices by default; you can use
185
+ # its block form to do so yourself:
186
+ #
187
+ # create_join_table :products, :categories do |t|
188
+ # t.index :product_id
189
+ # t.index :category_id
190
+ # end
191
+ #
192
+ # ====== Add a backend specific option to the generated SQL (MySQL)
193
+ #
194
+ # create_join_table(:assemblies, :parts, options: 'ENGINE=InnoDB DEFAULT CHARSET=utf8')
195
+ #
196
+ # generates:
197
+ #
198
+ # CREATE TABLE assemblies_parts (
199
+ # assembly_id bigint NOT NULL,
200
+ # part_id bigint NOT NULL,
201
+ # ) ENGINE=InnoDB DEFAULT CHARSET=utf8
202
+ #
203
+ def create_join_table(table_1, table_2, column_options: {}, **options)
204
+ ActiveRecord::Base.connection.create_join_table(table_1, table_2, column_options, **options)
205
+ end
206
+
207
+ # Drops a table from the database.
208
+ #
209
+ # [<tt>:force</tt>]
210
+ # Set to +:cascade+ to drop dependent objects as well.
211
+ # Defaults to false.
212
+ # [<tt>:if_exists</tt>]
213
+ # Set to +true+ to only drop the table if it exists.
214
+ # Defaults to false.
215
+ #
216
+ # Although this command ignores most +options+ and the block if one is given,
217
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
218
+ # In that case, +options+ and the block will be used by #create_table.
219
+ def drop_table(table_name, **options)
220
+ ActiveRecord::Base.connection.drop_table(table_name, **options)
221
+ end
222
+
223
+ # Drops the join table specified by the given arguments.
224
+ # See #create_join_table for details.
225
+ #
226
+ # Although this command ignores the block if one is given, it can be helpful
227
+ # to provide one in a migration's +change+ method so it can be reverted.
228
+ # In that case, the block will be used by #create_join_table.
229
+ def drop_join_table(table_1, table_2, **options)
230
+ ActiveRecord::Base.connection.drop_join_table(table_1, table_2, **options)
231
+ end
232
+
233
+ # Renames a table.
234
+ #
235
+ # rename_table('octopuses', 'octopi')
236
+ #
237
+ def rename_table(table_name, new_name)
238
+ ActiveRecord::Base.connection.rename_table(table_name, new_name)
239
+ end
240
+
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,550 @@
1
+ require 'active_support/concern'
2
+
3
+ module Crudboy
4
+ module Concerns
5
+ module TableDataDefinition
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+
10
+ # Add a new +type+ column named +column_name+ to +table_name+.
11
+ #
12
+ # The +type+ parameter is normally one of the migrations native types,
13
+ # which is one of the following:
14
+ # <tt>:primary_key</tt>, <tt>:string</tt>, <tt>:text</tt>,
15
+ # <tt>:integer</tt>, <tt>:bigint</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:numeric</tt>,
16
+ # <tt>:datetime</tt>, <tt>:time</tt>, <tt>:date</tt>,
17
+ # <tt>:binary</tt>, <tt>:boolean</tt>.
18
+ #
19
+ # You may use a type not in this list as long as it is supported by your
20
+ # database (for example, "polygon" in MySQL), but this will not be database
21
+ # agnostic and should usually be avoided.
22
+ #
23
+ # Available options are (none of these exists by default):
24
+ # * <tt>:limit</tt> -
25
+ # Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
26
+ # and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
27
+ # This option is ignored by some backends.
28
+ # * <tt>:default</tt> -
29
+ # The column's default value. Use +nil+ for +NULL+.
30
+ # * <tt>:null</tt> -
31
+ # Allows or disallows +NULL+ values in the column.
32
+ # * <tt>:precision</tt> -
33
+ # Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
34
+ # <tt>:datetime</tt>, and <tt>:time</tt> columns.
35
+ # * <tt>:scale</tt> -
36
+ # Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
37
+ # * <tt>:collation</tt> -
38
+ # Specifies the collation for a <tt>:string</tt> or <tt>:text</tt> column. If not specified, the
39
+ # column will have the same collation as the table.
40
+ # * <tt>:comment</tt> -
41
+ # Specifies the comment for the column. This option is ignored by some backends.
42
+ #
43
+ # Note: The precision is the total number of significant digits,
44
+ # and the scale is the number of digits that can be stored following
45
+ # the decimal point. For example, the number 123.45 has a precision of 5
46
+ # and a scale of 2. A decimal with a precision of 5 and a scale of 2 can
47
+ # range from -999.99 to 999.99.
48
+ #
49
+ # Please be aware of different RDBMS implementations behavior with
50
+ # <tt>:decimal</tt> columns:
51
+ # * The SQL standard says the default scale should be 0, <tt>:scale</tt> <=
52
+ # <tt>:precision</tt>, and makes no comments about the requirements of
53
+ # <tt>:precision</tt>.
54
+ # * MySQL: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..30].
55
+ # Default is (10,0).
56
+ # * PostgreSQL: <tt>:precision</tt> [1..infinity],
57
+ # <tt>:scale</tt> [0..infinity]. No default.
58
+ # * SQLite3: No restrictions on <tt>:precision</tt> and <tt>:scale</tt>,
59
+ # but the maximum supported <tt>:precision</tt> is 16. No default.
60
+ # * Oracle: <tt>:precision</tt> [1..38], <tt>:scale</tt> [-84..127].
61
+ # Default is (38,0).
62
+ # * DB2: <tt>:precision</tt> [1..63], <tt>:scale</tt> [0..62].
63
+ # Default unknown.
64
+ # * SqlServer: <tt>:precision</tt> [1..38], <tt>:scale</tt> [0..38].
65
+ # Default (38,0).
66
+ #
67
+ # == Examples
68
+ #
69
+ # User.add_column(:picture, :binary, limit: 2.megabytes)
70
+ # # ALTER TABLE "users" ADD "picture" blob(2097152)
71
+ #
72
+ # Article.add_column(:status, :string, limit: 20, default: 'draft', null: false)
73
+ # # ALTER TABLE "articles" ADD "status" varchar(20) DEFAULT 'draft' NOT NULL
74
+ #
75
+ # Answer.add_column(:bill_gates_money, :decimal, precision: 15, scale: 2)
76
+ # # ALTER TABLE "answers" ADD "bill_gates_money" decimal(15,2)
77
+ #
78
+ # Measurement.add_column(:sensor_reading, :decimal, precision: 30, scale: 20)
79
+ # # ALTER TABLE "measurements" ADD "sensor_reading" decimal(30,20)
80
+ #
81
+ # # While :scale defaults to zero on most databases, it
82
+ # # probably wouldn't hurt to include it.
83
+ # Measurement.add_column(:huge_integer, :decimal, precision: 30)
84
+ # # ALTER TABLE "measurements" ADD "huge_integer" decimal(30)
85
+ #
86
+ # # Defines a column that stores an array of a type.
87
+ # User.add_column(:skills, :text, array: true)
88
+ # # ALTER TABLE "users" ADD "skills" text[]
89
+ #
90
+ # # Defines a column with a database-specific type.
91
+ # Shape.add_column(:triangle, 'polygon')
92
+ # # ALTER TABLE "shapes" ADD "triangle" polygon
93
+ def add_column(column_name, type, **options)
94
+ ActiveRecord::Base.connection.add_column(table_name, column_name, type, **options)
95
+ end
96
+
97
+ # Changes the column's definition according to the new options.
98
+ # See TableDefinition#column for details of the options you can use.
99
+ #
100
+ # Supplier.change_column(:name, :string, limit: 80)
101
+ # Post.change_column(:description, :text)
102
+ #
103
+ def change_column(column_name, type, options = {})
104
+ ActiveRecord::Base.connection.change_column(table_name, column_name, type, options)
105
+ end
106
+
107
+ # Removes the column from the table definition.
108
+ #
109
+ # Supplier.remove_column(:qualification)
110
+ #
111
+ # The +type+ and +options+ parameters will be ignored if present. It can be helpful
112
+ # to provide these in a migration's +change+ method so it can be reverted.
113
+ # In that case, +type+ and +options+ will be used by #add_column.
114
+ # Indexes on the column are automatically removed.
115
+ def remove_column(column_name, type = nil, **options)
116
+ ActiveRecord::Base.connection.remove_column(table_name, column_name, type, **options)
117
+ end
118
+
119
+ # Adds a new index to the table. +column_name+ can be a single Symbol, or
120
+ # an Array of Symbols.
121
+ #
122
+ # The index will be named after the table and the column name(s), unless
123
+ # you pass <tt>:name</tt> as an option.
124
+ #
125
+ # ====== Creating a simple index
126
+ #
127
+ # Supplier.add_index(:name)
128
+ #
129
+ # generates:
130
+ #
131
+ # CREATE INDEX suppliers_name_index ON suppliers(name)
132
+ #
133
+ # ====== Creating a unique index
134
+ #
135
+ # Account.add_index([:branch_id, :party_id], unique: true)
136
+ #
137
+ # generates:
138
+ #
139
+ # CREATE UNIQUE INDEX accounts_branch_id_party_id_index ON accounts(branch_id, party_id)
140
+ #
141
+ # ====== Creating a named index
142
+ #
143
+ # Account.add_index([:branch_id, :party_id], unique: true, name: 'by_branch_party')
144
+ #
145
+ # generates:
146
+ #
147
+ # CREATE UNIQUE INDEX by_branch_party ON accounts(branch_id, party_id)
148
+ #
149
+ # ====== Creating an index with specific key length
150
+ #
151
+ # Account.add_index(:name, name: 'by_name', length: 10)
152
+ #
153
+ # generates:
154
+ #
155
+ # CREATE INDEX by_name ON accounts(name(10))
156
+ #
157
+ # ====== Creating an index with specific key lengths for multiple keys
158
+ #
159
+ # Account.add_index([:name, :surname], name: 'by_name_surname', length: {name: 10, surname: 15})
160
+ #
161
+ # generates:
162
+ #
163
+ # CREATE INDEX by_name_surname ON accounts(name(10), surname(15))
164
+ #
165
+ # Note: SQLite doesn't support index length.
166
+ #
167
+ # ====== Creating an index with a sort order (desc or asc, asc is the default)
168
+ #
169
+ # Account.add_index([:branch_id, :party_id, :surname], order: {branch_id: :desc, party_id: :asc})
170
+ #
171
+ # generates:
172
+ #
173
+ # CREATE INDEX by_branch_desc_party ON accounts(branch_id DESC, party_id ASC, surname)
174
+ #
175
+ # Note: MySQL only supports index order from 8.0.1 onwards (earlier versions accepted the syntax but ignored it).
176
+ #
177
+ # ====== Creating a partial index
178
+ #
179
+ # Account.add_index([:branch_id, :party_id], unique: true, where: "active")
180
+ #
181
+ # generates:
182
+ #
183
+ # CREATE UNIQUE INDEX index_accounts_on_branch_id_and_party_id ON accounts(branch_id, party_id) WHERE active
184
+ #
185
+ # Note: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
186
+ #
187
+ # ====== Creating an index with a specific method
188
+ #
189
+ # Developer.add_index(:name, using: 'btree')
190
+ #
191
+ # generates:
192
+ #
193
+ # CREATE INDEX index_developers_on_name ON developers USING btree (name) -- PostgreSQL
194
+ # CREATE INDEX index_developers_on_name USING btree ON developers (name) -- MySQL
195
+ #
196
+ # Note: only supported by PostgreSQL and MySQL
197
+ #
198
+ # ====== Creating an index with a specific operator class
199
+ #
200
+ # Developer.add_index(:name, using: 'gist', opclass: :gist_trgm_ops)
201
+ # # CREATE INDEX developers_on_name ON developers USING gist (name gist_trgm_ops) -- PostgreSQL
202
+ #
203
+ # Developer.add_index([:name, :city], using: 'gist', opclass: { city: :gist_trgm_ops })
204
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name, city gist_trgm_ops) -- PostgreSQL
205
+ #
206
+ # Developer.add_index([:name, :city], using: 'gist', opclass: :gist_trgm_ops)
207
+ # # CREATE INDEX developers_on_name_and_city ON developers USING gist (name gist_trgm_ops, city gist_trgm_ops) -- PostgreSQL
208
+ #
209
+ # Note: only supported by PostgreSQL
210
+ #
211
+ # ====== Creating an index with a specific type
212
+ #
213
+ # Developer.add_index(:name, type: :fulltext)
214
+ #
215
+ # generates:
216
+ #
217
+ # CREATE FULLTEXT INDEX index_developers_on_name ON developers (name) -- MySQL
218
+ #
219
+ # Note: only supported by MySQL.
220
+ #
221
+ # ====== Creating an index with a specific algorithm
222
+ #
223
+ # Developer.add_index(:name, algorithm: :concurrently)
224
+ # # CREATE INDEX CONCURRENTLY developers_on_name on developers (name)
225
+ #
226
+ # Note: only supported by PostgreSQL.
227
+ #
228
+ # Concurrently adding an index is not supported in a transaction.
229
+ #
230
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
231
+ def add_index(column_name, options = {})
232
+ ActiveRecord::Base.connection.add_index(table_name, column_name, options)
233
+ end
234
+
235
+ # Adds a new foreign key.
236
+ # +to_table+ contains the referenced primary key.
237
+ #
238
+ # The foreign key will be named after the following pattern: <tt>fk_rails_<identifier></tt>.
239
+ # +identifier+ is a 10 character long string which is deterministically generated from this
240
+ # table and +column+. A custom name can be specified with the <tt>:name</tt> option.
241
+ #
242
+ # ====== Creating a simple foreign key
243
+ #
244
+ # Article.add_foreign_key :authors
245
+ #
246
+ # generates:
247
+ #
248
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id")
249
+ #
250
+ # ====== Creating a foreign key on a specific column
251
+ #
252
+ # Article.add_foreign_key :users, column: :author_id, primary_key: "lng_id"
253
+ #
254
+ # generates:
255
+ #
256
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_58ca3d3a82 FOREIGN KEY ("author_id") REFERENCES "users" ("lng_id")
257
+ #
258
+ # ====== Creating a cascading foreign key
259
+ #
260
+ # Article.add_foreign_key :authors, on_delete: :cascade
261
+ #
262
+ # generates:
263
+ #
264
+ # ALTER TABLE "articles" ADD CONSTRAINT fk_rails_e74ce85cbc FOREIGN KEY ("author_id") REFERENCES "authors" ("id") ON DELETE CASCADE
265
+ #
266
+ # The +options+ hash can include the following keys:
267
+ # [<tt>:column</tt>]
268
+ # The foreign key column name on +from_table+. Defaults to <tt>to_table.singularize + "_id"</tt>
269
+ # [<tt>:primary_key</tt>]
270
+ # The primary key column name on +to_table+. Defaults to +id+.
271
+ # [<tt>:name</tt>]
272
+ # The constraint name. Defaults to <tt>fk_rails_<identifier></tt>.
273
+ # [<tt>:on_delete</tt>]
274
+ # Action that happens <tt>ON DELETE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
275
+ # [<tt>:on_update</tt>]
276
+ # Action that happens <tt>ON UPDATE</tt>. Valid values are +:nullify+, +:cascade+ and +:restrict+
277
+ # [<tt>:validate</tt>]
278
+ # (PostgreSQL only) Specify whether or not the constraint should be validated. Defaults to +true+.
279
+ def add_foreign_key(to_table, **options)
280
+ ActiveRecord::Base.connection.add_foreign_key(table_name, to_table, **options)
281
+ end
282
+
283
+ # Adds timestamps (+created_at+ and +updated_at+) columns to this table.
284
+ # Additional options (like +:null+) are forwarded to #add_column.
285
+ #
286
+ # Supplier.add_timestamps(null: true)
287
+ #
288
+ def add_timestamps(**options)
289
+ ActiveRecord::Base.connection.add_timestamps(table_name, **options)
290
+ end
291
+
292
+ # Changes the comment for a column or removes it if +nil+.
293
+ #
294
+ # Passing a hash containing +:from+ and +:to+ will make this change
295
+ # reversible in migration:
296
+ #
297
+ # Post.change_column_comment(:state, from: "old_comment", to: "new_comment")
298
+ def change_column_comment(column_name, comment_or_changes)
299
+ ActiveRecord::Base.connection.change_column_comment(table_name, column_name, comment_or_changes)
300
+ end
301
+
302
+ # Sets a new default value for a column:
303
+ #
304
+ # Supplier.change_column_default(:qualification, 'new')
305
+ # change_column_default(:accounts, :authorized, 1)
306
+ #
307
+ # Setting the default to +nil+ effectively drops the default:
308
+ #
309
+ # User.change_column_default(:email, nil)
310
+ #
311
+ # Passing a hash containing +:from+ and +:to+ will make this change
312
+ # reversible in migration:
313
+ #
314
+ # Post.change_column_default(:state, from: nil, to: "draft")
315
+ #
316
+ def change_column_default(column_name, default_or_changes)
317
+ ActiveRecord::Base.connection.change_column_default(table_name, column_name, default_or_changes)
318
+ end
319
+
320
+ # Sets or removes a <tt>NOT NULL</tt> constraint on a column. The +null+ flag
321
+ # indicates whether the value can be +NULL+. For example
322
+ #
323
+ # User.change_column_null(:nickname, false)
324
+ #
325
+ # says nicknames cannot be +NULL+ (adds the constraint), whereas
326
+ #
327
+ # User.change_column_null(:nickname, true)
328
+ #
329
+ # allows them to be +NULL+ (drops the constraint).
330
+ #
331
+ # The method accepts an optional fourth argument to replace existing
332
+ # <tt>NULL</tt>s with some other value. Use that one when enabling the
333
+ # constraint if needed, since otherwise those rows would not be valid.
334
+ #
335
+ # Please note the fourth argument does not set a column's default.
336
+ def change_column_null(column_name, null, default = nil)
337
+ ActiveRecord::Base.connection.change_column_null(table_name, column_name, null, default)
338
+ end
339
+
340
+ # Renames a column.
341
+ #
342
+ # Supplier.rename_column(:description, :name)
343
+ #
344
+ def rename_column(column_name, new_column_name)
345
+ ActiveRecord::Base.connection.rename_column(table_name, column_name, new_column_name)
346
+ end
347
+
348
+ # A block for changing columns in +table+.
349
+ #
350
+ # # change_table() yields a Table instance
351
+ # Supplier.change_table do |t|
352
+ # t.column :name, :string, limit: 60
353
+ # # Other column alterations here
354
+ # end
355
+ #
356
+ # The +options+ hash can include the following keys:
357
+ # [<tt>:bulk</tt>]
358
+ # Set this to true to make this a bulk alter query, such as
359
+ #
360
+ # ALTER TABLE `users` ADD COLUMN age INT, ADD COLUMN birthdate DATETIME ...
361
+ #
362
+ # Defaults to false.
363
+ #
364
+ # Only supported on the MySQL and PostgreSQL adapter, ignored elsewhere.
365
+ #
366
+ # ====== Add a column
367
+ #
368
+ # Supplier.change_table do |t|
369
+ # t.column :name, :string, limit: 60
370
+ # end
371
+ #
372
+ # ====== Add 2 integer columns
373
+ #
374
+ # Supplier.change_table do |t|
375
+ # t.integer :width, :height, null: false, default: 0
376
+ # end
377
+ #
378
+ # ====== Add created_at/updated_at columns
379
+ #
380
+ # Supplier.change_table do |t|
381
+ # t.timestamps
382
+ # end
383
+ #
384
+ # ====== Add a foreign key column
385
+ #
386
+ # Supplier.change_table do |t|
387
+ # t.references :company
388
+ # end
389
+ #
390
+ # Creates a <tt>company_id(bigint)</tt> column.
391
+ #
392
+ # ====== Add a polymorphic foreign key column
393
+ #
394
+ # Supplier.change_table do |t|
395
+ # t.belongs_to :company, polymorphic: true
396
+ # end
397
+ #
398
+ # Creates <tt>company_type(varchar)</tt> and <tt>company_id(bigint)</tt> columns.
399
+ #
400
+ # ====== Remove a column
401
+ #
402
+ # Supplier.change_table do |t|
403
+ # t.remove :company
404
+ # end
405
+ #
406
+ # ====== Remove several columns
407
+ #
408
+ # Supplier.change_table do |t|
409
+ # t.remove :company_id
410
+ # t.remove :width, :height
411
+ # end
412
+ #
413
+ # ====== Remove an index
414
+ #
415
+ # Supplier.change_table do |t|
416
+ # t.remove_index :company_id
417
+ # end
418
+ #
419
+ # See also Table for details on all of the various column transformations.
420
+ def change_table(**options)
421
+ ActiveRecord::Base.connection.change_table(table_name, **options)
422
+ end
423
+
424
+ # Renames a table.
425
+ #
426
+ # rename_table('octopi')
427
+ #
428
+ def rename_table(new_name)
429
+ ActiveRecord::Base.connection.rename_table(table_name, new_name)
430
+ end
431
+
432
+ # Changes the comment for a table or removes it if +nil+.
433
+ #
434
+ # Passing a hash containing +:from+ and +:to+ will make this change
435
+ # reversible in migration:
436
+ #
437
+ # Post.change_table_comment(from: "old_comment", to: "new_comment")
438
+ def change_table_comment(comment_or_changes)
439
+ ActiveRecord::Base.connection.change_table_comment(table_name, comment_or_changes)
440
+ end
441
+
442
+ # Drops a table from the database.
443
+ #
444
+ # [<tt>:force</tt>]
445
+ # Set to +:cascade+ to drop dependent objects as well.
446
+ # Defaults to false.
447
+ # [<tt>:if_exists</tt>]
448
+ # Set to +true+ to only drop the table if it exists.
449
+ # Defaults to false.
450
+ #
451
+ # Although this command ignores most +options+ and the block if one is given,
452
+ # it can be helpful to provide these in a migration's +change+ method so it can be reverted.
453
+ # In that case, +options+ and the block will be used by #create_table.
454
+ def drop_table(**options)
455
+ ActiveRecord::Base.connection.drop_table(table_name, **options)
456
+ end
457
+
458
+ # Returns an array of foreign keys for the given table.
459
+ # The foreign keys are represented as ForeignKeyDefinition objects.
460
+ def foreign_keys
461
+ ActiveRecord::Base.connection.foreign_keys(table_name)
462
+ end
463
+
464
+ # Removes the given foreign key from the table. Any option parameters provided
465
+ # will be used to re-add the foreign key in case of a migration rollback.
466
+ # It is recommended that you provide any options used when creating the foreign
467
+ # key so that the migration can be reverted properly.
468
+ #
469
+ # Removes the foreign key on +accounts.branch_id+.
470
+ #
471
+ # Account.remove_foreign_key :branches
472
+ #
473
+ # Removes the foreign key on +accounts.owner_id+.
474
+ #
475
+ # Account.remove_foreign_key column: :owner_id
476
+ #
477
+ # Removes the foreign key on +accounts.owner_id+.
478
+ #
479
+ # Account.remove_foreign_key to_table: :owners
480
+ #
481
+ # Removes the foreign key named +special_fk_name+ on the +accounts+ table.
482
+ #
483
+ # Account.remove_foreign_key name: :special_fk_name
484
+ #
485
+ # The +options+ hash accepts the same keys as SchemaStatements#add_foreign_key
486
+ # with an addition of
487
+ # [<tt>:to_table</tt>]
488
+ # The name of the table that contains the referenced primary key.
489
+ def remove_foreign_key(to_table = nil, **options)
490
+ ActiveRecord::Base.connection.remove_foreign_key(table_name, to_table, **options)
491
+ end
492
+
493
+ # Removes the given index from the table.
494
+ #
495
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
496
+ #
497
+ # Account.remove_index :branch_id
498
+ #
499
+ # Removes the index on +branch_id+ in the +accounts+ table if exactly one such index exists.
500
+ #
501
+ # Account.remove_index column: :branch_id
502
+ #
503
+ # Removes the index on +branch_id+ and +party_id+ in the +accounts+ table if exactly one such index exists.
504
+ #
505
+ # Account.remove_index column: [:branch_id, :party_id]
506
+ #
507
+ # Removes the index named +by_branch_party+ in the +accounts+ table.
508
+ #
509
+ # Account.remove_index name: :by_branch_party
510
+ #
511
+ # Removes the index named +by_branch_party+ in the +accounts+ table +concurrently+.
512
+ #
513
+ # Account.remove_index name: :by_branch_party, algorithm: :concurrently
514
+ #
515
+ # Note: only supported by PostgreSQL.
516
+ #
517
+ # Concurrently removing an index is not supported in a transaction.
518
+ #
519
+ # For more information see the {"Transactional Migrations" section}[rdoc-ref:Migration].
520
+ def remove_index(options = {})
521
+ ActiveRecord::Base.connection.remove_index(table_name, options)
522
+ end
523
+
524
+ # Removes the timestamp columns (+created_at+ and +updated_at+) from the table definition.
525
+ #
526
+ # Supplier.remove_timestamps
527
+ #
528
+ def remove_timestamps(**options)
529
+ ActiveRecord::Base.connection.remove_timestamps(**options)
530
+ end
531
+
532
+ # Renames an index.
533
+ #
534
+ # Rename the +index_people_on_last_name+ index to +index_users_on_last_name+:
535
+ #
536
+ # Person.rename_index 'index_people_on_last_name', 'index_users_on_last_name'
537
+ #
538
+ def rename_index(old_name, new_name)
539
+ ActiveRecord::Base.connection.rename_index(table_name, old_name, new_name)
540
+ end
541
+
542
+ # Returns the table comment that's stored in database metadata.
543
+ def table_comment
544
+ ActiveRecord::Base.connection.table_comment(table_name)
545
+ end
546
+
547
+ end
548
+ end
549
+ end
550
+ end