schema_plus_core 0.1.0 → 0.2.0
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 +4 -4
- data/README.md +64 -54
- data/lib/schema_plus/core/active_record/connection_adapters/abstract_adapter.rb +6 -0
- data/lib/schema_plus/core/active_record/connection_adapters/mysql2_adapter.rb +6 -0
- data/lib/schema_plus/core/active_record/connection_adapters/postgresql_adapter.rb +6 -0
- data/lib/schema_plus/core/middleware.rb +6 -0
- data/lib/schema_plus/core/version.rb +1 -1
- data/spec/dumper_spec.rb +1 -1
- data/spec/middleware_spec.rb +4 -0
- data/spec/support/test_reporter.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: be82dc70c1337ef9fec4f7bcc88ad9011455bf86
|
4
|
+
data.tar.gz: fb4f3f8aa09a494fc535b8c4e337f08ef43994c4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59e8ed21ad18ee6dfbed44d4d70b2700a9617044a96d87746c61b8ca3d54aa4911d7c3c147fd7afa38ff8da0ee6242d9f5f0570b25aed6c1593d5db186768e49
|
7
|
+
data.tar.gz: 53e646d5d164ce111e58b137dd0c4e07b129429699de0c913a638971ca94ad53076725ac9e2f28f266b6fe48a6b258bcbbcde384d1b3d0f23bc66bb9efe5693e
|
data/README.md
CHANGED
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
SchemaPlus::Core creates an internal extension API to ActiveRecord. The idea is that:
|
9
9
|
|
10
|
-
* ShemaPlus::Core does the monkey-patching so clients don't have to know too much about the internal of ActiveRecord.
|
10
|
+
* ShemaPlus::Core does the monkey-patching so clients don't have to know too much about the internal of ActiveRecord.
|
11
11
|
|
12
12
|
* SchemaPlus::Core's extension API is consistent across the various connection adapters, so clients don't have to figure out how to extend each connection adapter independently.
|
13
13
|
|
@@ -15,7 +15,7 @@ SchemaPlus::Core creates an internal extension API to ActiveRecord. The idea is
|
|
15
15
|
|
16
16
|
By itself, SchemaPlus::Core does not change any behavior or add any external features to ActiveRecord. It just makes the API available to clients.
|
17
17
|
|
18
|
-
SchemaPlus::Core is a client of [schema_monkey](https://github.com/SchemaPlus/schema_monkey), using [modware](https://github.com/ronen/modware) to define middleware callback stacks.
|
18
|
+
SchemaPlus::Core is a client of [schema_monkey](https://github.com/SchemaPlus/schema_monkey), using [modware](https://github.com/ronen/modware) to define middleware callback stacks.
|
19
19
|
|
20
20
|
|
21
21
|
## Compatibility
|
@@ -31,8 +31,6 @@ SchemaPlus::Core is tested on:
|
|
31
31
|
|
32
32
|
## Installation
|
33
33
|
|
34
|
-
As usual:
|
35
|
-
|
36
34
|
<!-- SCHEMA_DEV: TEMPLATE INSTALLATION - begin -->
|
37
35
|
<!-- These lines are auto-inserted from a schema_dev template -->
|
38
36
|
As usual:
|
@@ -65,7 +63,7 @@ module MyClient
|
|
65
63
|
#
|
66
64
|
# Middleware modules to insert in SchemaPlus::Core API stacks
|
67
65
|
#
|
68
|
-
end
|
66
|
+
end
|
69
67
|
module ActiveRecord
|
70
68
|
#
|
71
69
|
# direct ActiveRecord enhancements, should your client need any.
|
@@ -83,7 +81,7 @@ require "schema_plus/core"
|
|
83
81
|
|
84
82
|
module AutoUniquify
|
85
83
|
module Middleware
|
86
|
-
|
84
|
+
|
87
85
|
module Migration
|
88
86
|
module Index
|
89
87
|
def before(env)
|
@@ -91,7 +89,7 @@ module AutoUniquify
|
|
91
89
|
end
|
92
90
|
end
|
93
91
|
end
|
94
|
-
|
92
|
+
|
95
93
|
end
|
96
94
|
end
|
97
95
|
|
@@ -118,13 +116,13 @@ Stacks for general queries pertaining to the entire database schema:
|
|
118
116
|
`:connection` | The current ActiveRecord connection | *context*
|
119
117
|
`:table_name` | The name of the table to query | *arg*
|
120
118
|
`:query_name` | Label sometimes used by ActiveRecord logging | *arg*
|
121
|
-
|
119
|
+
|
122
120
|
The base implementation appends its results to `env.index_definitions`
|
123
121
|
|
124
122
|
* `Schema::Tables`
|
125
123
|
|
126
124
|
Wrapper around the `connection.tables()` method. Env contains:
|
127
|
-
|
125
|
+
|
128
126
|
Env Field | Description | Initialized
|
129
127
|
--- | --- | ---
|
130
128
|
`:tables` | The result of the lookup | `[]`
|
@@ -133,9 +131,9 @@ Stacks for general queries pertaining to the entire database schema:
|
|
133
131
|
`:database` | (Mysql only) | *arg*
|
134
132
|
`:like` | (Mysql only) | *arg*
|
135
133
|
`:query_name` | Label sometimes used by ActiveRecord logging | *arg*
|
136
|
-
|
134
|
+
|
137
135
|
The base implementation appends its results to `env.tables`
|
138
|
-
|
136
|
+
|
139
137
|
### Model
|
140
138
|
|
141
139
|
Stacks for class methods on ActiveRecord models.
|
@@ -143,32 +141,32 @@ Stacks for class methods on ActiveRecord models.
|
|
143
141
|
* `Model::Columns`
|
144
142
|
|
145
143
|
Wrapper around the `Model.columns` query
|
146
|
-
|
144
|
+
|
147
145
|
Env Field | Description | Initialized
|
148
146
|
--- | --- | ---
|
149
147
|
`:columns` | The resulting Column objects | `[]`
|
150
148
|
`:model` | The model Class being queried | *context*
|
151
149
|
|
152
150
|
The base implementation appends its results to `env.columns`
|
153
|
-
|
151
|
+
|
154
152
|
* `Model::ResetColumnInformation`
|
155
153
|
|
156
154
|
Wrapper around the `Model.reset_column_information` method
|
157
|
-
|
155
|
+
|
158
156
|
Env Field | Description | Initialized
|
159
157
|
--- | --- | ---
|
160
158
|
`:model` | The model Class being reset | *context*
|
161
|
-
|
159
|
+
|
162
160
|
The base implementation performs the reset.
|
163
161
|
|
164
162
|
### Migration
|
165
163
|
|
166
|
-
Stacks for operations that
|
164
|
+
Stacks for operations that change the schema. In some cases the operation immediately modifies the database schema, in others the operation defines ActiveRecord objects (e.g., column definitions in a create_table definition) and the actual modification of the database schema will happen some time later.
|
167
165
|
|
168
166
|
* `Migration::Column`
|
169
167
|
|
170
168
|
Callback stack for various ways to define or modify a column.
|
171
|
-
|
169
|
+
|
172
170
|
Env Field | Description | Initialized
|
173
171
|
--- | --- | ---
|
174
172
|
`:caller` | The ActiveRecord instance responsible for performing the action | *context*
|
@@ -179,24 +177,36 @@ Stacks for operations that define database objects in migrations and schema load
|
|
179
177
|
`:options` | The column options | *arg*, default `{}`
|
180
178
|
|
181
179
|
The base implementation performs the column operation. No value is returned.
|
182
|
-
|
180
|
+
|
183
181
|
Notes:
|
184
|
-
|
182
|
+
|
185
183
|
1. The `:operation` field has the following meanings:
|
186
|
-
|
184
|
+
|
187
185
|
* `:add` - The column will be added immediately (`Migration#add_column`)
|
188
186
|
* `:change` - The column will be changed immediately (`Migration#change_column`)
|
189
187
|
* `:define` - The column will be added to table definition, which will be emitted later
|
190
188
|
* `:record` - The column info will be added to a migration command recorder, for later playback in reverse by `Migration#down`
|
191
189
|
|
192
190
|
2. In the case of a table definition using `t.references` or `t.belongs_to`, the `:type` field will be set to `:reference` and the `:column_name` will include the `"_id"` suffix
|
193
|
-
|
191
|
+
|
194
192
|
3. ActiveRecord's base implementation may make nested calls to column creation. For example: References result in a nested call to create an integer column; Polymorphic references nest calls to create two columns; Sqlite3 implements `:change` by a nested call to a new table definition. SchemaPlus::Core doesn't attempt to normalize or suppress these; each such nested call will result in its own `Migration::Column` stack execution.
|
195
|
-
|
193
|
+
|
194
|
+
* `Migration::DropTable`
|
195
|
+
|
196
|
+
Drops a table from the database
|
197
|
+
|
198
|
+
Env Field | Description | Initialized
|
199
|
+
--- | --- | ---
|
200
|
+
`:connection` | The current ActiveRecord connection | *context*
|
201
|
+
`:table_name` | The name of the table | *arg*
|
202
|
+
`:options` | The index options | *arg*
|
203
|
+
|
204
|
+
The base implementation drops the table. No value is returned.
|
205
|
+
|
196
206
|
* `Migration::Index`
|
197
207
|
|
198
|
-
Callback stack for various ways to define an index
|
199
|
-
|
208
|
+
Callback stack for various ways to define an index.
|
209
|
+
|
200
210
|
Env Field | Description | Initialized
|
201
211
|
--- | --- | ---
|
202
212
|
`:caller` | The ActiveRecord instance responsible for performing the action | *context*
|
@@ -206,11 +216,11 @@ Stacks for operations that define database objects in migrations and schema load
|
|
206
216
|
`:options` | The index options | *arg*, default `{}`
|
207
217
|
|
208
218
|
The base implementation performs the index creation operation. No value is returned.
|
209
|
-
|
219
|
+
|
210
220
|
Notes:
|
211
|
-
|
221
|
+
|
212
222
|
1. The `:operation` field has the following meanings:
|
213
|
-
|
223
|
+
|
214
224
|
* `:add` - The index will be added immediately (`Migration#add_index`)
|
215
225
|
* `:define` - The index will be added to a table definition, which will be emitted later.
|
216
226
|
|
@@ -221,7 +231,7 @@ Stacks for internal operations that generate SQL.
|
|
221
231
|
* `Sql::ColumnOptions`
|
222
232
|
|
223
233
|
Callback stack around generation of the SQL options for a column definition.
|
224
|
-
|
234
|
+
|
225
235
|
Env Field | Description | Initialized
|
226
236
|
--- | --- | ---
|
227
237
|
`:sql` | The resulting SQL | `""`
|
@@ -235,7 +245,7 @@ Stacks for internal operations that generate SQL.
|
|
235
245
|
* `Sql::IndexComponents`
|
236
246
|
|
237
247
|
Callback stack around generation of the SQL for an index definition.
|
238
|
-
|
248
|
+
|
239
249
|
Env Field | Description | Initialized
|
240
250
|
--- | --- | ---
|
241
251
|
`:sql` | The resulting SQL components, in a struct with fields `:name`, `:type`, `:columns`, `:options`, `:algorithm`, `:using` | *empty struct*
|
@@ -245,30 +255,30 @@ Stacks for internal operations that generate SQL.
|
|
245
255
|
`:options` | The index options | *context*
|
246
256
|
|
247
257
|
The base implementation *overwrites* the contents of `env.sql`
|
248
|
-
|
258
|
+
|
249
259
|
Notes:
|
250
|
-
|
260
|
+
|
251
261
|
1. SQLite3 ignores the `:type`, `:algoritm`, and `:using` fields of `env.sql`
|
252
262
|
|
253
263
|
* `Sql::Table`
|
254
264
|
|
255
265
|
Callback stack around generation of the SQL for a table
|
256
|
-
|
266
|
+
|
257
267
|
Env Field | Description | Initialized
|
258
268
|
--- | --- | ---
|
259
269
|
`:sql` | The resulting SQL components in a struct with fields `:command`, `:name`, `:body`, `:options`, `:quotechar` | *empty struct*
|
260
270
|
`:caller` | The ActiveRecord::SchemaCreation instance | *context*
|
261
271
|
`:connection` | The current ActiveRecord connection | *context*
|
262
272
|
`:table_definition` | The TableDefinition object for the table | *context*
|
263
|
-
|
273
|
+
|
264
274
|
The base implementation *overwrites* the contents of `env.sql`
|
265
|
-
|
275
|
+
|
266
276
|
Notes:
|
267
|
-
|
277
|
+
|
268
278
|
1. `env.sql.command` contains the index creation command such as `CREATE TABLE` or `CREATE TEMPORARY TABLE`
|
269
279
|
2. `env.sql.quotechar` contains the quote character ', ", or \` to wrap `env.sql.name` in.
|
270
280
|
|
271
|
-
|
281
|
+
|
272
282
|
### Query
|
273
283
|
|
274
284
|
Stacks around low-level query execution
|
@@ -276,7 +286,7 @@ Stacks around low-level query execution
|
|
276
286
|
* `Query::Exec`
|
277
287
|
|
278
288
|
Callback stack wraps the emission of sql to the underlying dbms gem.
|
279
|
-
|
289
|
+
|
280
290
|
Env Field | Description | Initialized
|
281
291
|
--- | --- | ---
|
282
292
|
`:result` | The result of the database query | *unset*
|
@@ -284,28 +294,28 @@ Stacks around low-level query execution
|
|
284
294
|
`:sql` | The SQL string | *context*
|
285
295
|
`:binds` | Values to substitute into the SQL string
|
286
296
|
`:query_name` | Label sometimes used by ActiveRecord logging | *arg*
|
287
|
-
|
288
|
-
|
297
|
+
|
298
|
+
|
289
299
|
### Dumper
|
290
300
|
|
291
301
|
SchemaPlus::Core provides a state object and of callbacks to various phases of the schema dumping process. The dumping process fleshes out the state object-- nothing is actually written to the dump file until after the state is fleshed out.
|
292
302
|
|
293
303
|
#### Schema Dump state
|
294
304
|
|
295
|
-
* `Class SchemaPlus::Core::SchemaDump`
|
305
|
+
* `Class SchemaPlus::Core::SchemaDump`
|
296
306
|
|
297
307
|
An instance of `SchemaPlus::Core::SchemaDump` gets passed to each of the callback stacks; the dump gets built up by fleshing out its contents. `SchemaDump` has the following fields and methods:
|
298
|
-
|
308
|
+
|
299
309
|
* `dump.initial = []` - an array of strings containing statements to start the schema with, such as `enable_extension 'hstore'`
|
300
310
|
* `dump.data = OpenStruct.new` - a place for clients to store arbitrary data between phases
|
301
311
|
* `dump.tables = {}` - a hash mapping table names to SchemaDump::Table objects
|
302
312
|
* `dump.final = []` - an array of strings containing statements to end the schema with.
|
303
313
|
* `dump.depends(table_name, [prerequisite_table_names])` - call this method to ensure that the definition of `table_name` won't be output before its prerequisites.
|
304
|
-
|
314
|
+
|
305
315
|
* `Class SchemaPlus::Core::SchemaDump::Table`
|
306
|
-
|
316
|
+
|
307
317
|
Each table in the dump has its contents in a SchemaDump::Table object, with these fields:
|
308
|
-
|
318
|
+
|
309
319
|
* `table.name` - the table name
|
310
320
|
* `table.pname` - table as actually used in SQL (without any prefixes or suffixes)
|
311
321
|
* `table.options` - a string containing the options to `Migration.create_table`
|
@@ -317,7 +327,7 @@ SchemaPlus::Core provides a state object and of callbacks to various phases of t
|
|
317
327
|
* `Class SchemaPlus::Core::SchemaDump::Table::Column`
|
318
328
|
|
319
329
|
Each column in a table has its contents in a SchemaDump::Table::Column object, with these fields and methods:
|
320
|
-
|
330
|
+
|
321
331
|
* `column.name` - the column name
|
322
332
|
* `column.type` - the column type (i.e., what comes after `"t."`)
|
323
333
|
* `column.options` - an optional string containing the options for the column
|
@@ -328,25 +338,25 @@ SchemaPlus::Core provides a state object and of callbacks to various phases of t
|
|
328
338
|
* `Class SchemaPlus::Core::SchemaDump::Table::Index`
|
329
339
|
|
330
340
|
Each index in a table has its contents in a SchemaDump::Table::Index object, with these fields and methods:
|
331
|
-
|
341
|
+
|
332
342
|
* `index.name` - the index name
|
333
343
|
* `index.columns` - the columns that are in the index
|
334
344
|
* `index.options` - an optional string containing the options for the index
|
335
345
|
* `index.add_option(option)` - adds an option to the current string, separating with a "," if the current set isn't blank
|
336
346
|
|
337
|
-
#### Schema Dump Middleware stacks
|
347
|
+
#### Schema Dump Middleware stacks
|
338
348
|
|
339
349
|
* `Dumper::Initial`
|
340
350
|
|
341
351
|
Callback stack wraps the creation of initial statements for the dump.
|
342
|
-
|
352
|
+
|
343
353
|
Env Field | Description | Initialized
|
344
354
|
--- | --- | ---
|
345
355
|
`:initial` | The initial statements | []
|
346
356
|
`:dump` | The SchemaDump object | *context*
|
347
357
|
`:dumper` | The current ActiveRecord::SchemaDumper instance| *context*
|
348
358
|
`:connection` | The current ActiveRecord connection | *context*
|
349
|
-
|
359
|
+
|
350
360
|
The base method appends initial statements to `env.initial`.
|
351
361
|
|
352
362
|
* `Dumper::Tables`
|
@@ -358,7 +368,7 @@ SchemaPlus::Core provides a state object and of callbacks to various phases of t
|
|
358
368
|
`:dump` | The SchemaDump object | *context*
|
359
369
|
`:dumper` | The current ActiveRecord::SchemaDumper instance| *context*
|
360
370
|
`:connection` | The current ActiveRecord connection | *context*
|
361
|
-
|
371
|
+
|
362
372
|
The base method iterates through all tables, dumping each.
|
363
373
|
|
364
374
|
|
@@ -372,11 +382,11 @@ SchemaPlus::Core provides a state object and of callbacks to various phases of t
|
|
372
382
|
`:dump` | The SchemaDump object | *context*
|
373
383
|
`:dumper` | The current ActiveRecord::SchemaDumper instance| *context*
|
374
384
|
`:connection` | The current ActiveRecord connection | *context*
|
375
|
-
|
376
|
-
The base method iterates through all columns and indexes of the table, and *overwrites* the contents of `table`,
|
377
|
-
|
385
|
+
|
386
|
+
The base method iterates through all columns and indexes of the table, and *overwrites* the contents of `table`,
|
387
|
+
|
378
388
|
Notes:
|
379
|
-
|
389
|
+
|
380
390
|
1. When the stack is called, `env.dump.tables[env.table.name]` contains the `env.table` object.
|
381
391
|
|
382
392
|
* `Dumper::Indexes`
|
@@ -16,6 +16,12 @@ module SchemaPlus
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def drop_table(table_name, options={})
|
20
|
+
SchemaMonkey::Middleware::Migration::DropTable.start(connection: self, table_name: table_name, options: options.dup) do |env|
|
21
|
+
super env.table_name, env.options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
19
25
|
def add_index_options(table_name, column_names, options={})
|
20
26
|
SchemaMonkey::Middleware::Sql::IndexComponents.start(connection: self, table_name: table_name, column_names: Array.wrap(column_names), options: options.deep_dup, sql: SqlStruct::IndexComponents.new) { |env|
|
21
27
|
env.sql.name, env.sql.type, env.sql.columns, env.sql.options, env.sql.algorithm, env.sql.using = super env.table_name, env.column_names, env.options
|
@@ -16,6 +16,12 @@ module SchemaPlus
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def drop_table(table_name, options={})
|
20
|
+
SchemaMonkey::Middleware::Migration::DropTable.start(connection: self, table_name: table_name, options: options.dup) do |env|
|
21
|
+
super env.table_name, env.options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
19
25
|
def indexes(table_name, query_name=nil)
|
20
26
|
SchemaMonkey::Middleware::Schema::Indexes.start(connection: self, table_name: table_name, query_name: query_name, index_definitions: []) { |env|
|
21
27
|
env.index_definitions += super env.table_name, env.query_name
|
@@ -16,6 +16,12 @@ module SchemaPlus
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
19
|
+
def drop_table(table_name, options={})
|
20
|
+
SchemaMonkey::Middleware::Migration::DropTable.start(connection: self, table_name: table_name, options: options.dup) do |env|
|
21
|
+
super env.table_name, env.options
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
19
25
|
def exec_cache(sql, name, binds)
|
20
26
|
SchemaMonkey::Middleware::Query::Exec.start(connection: self, sql: sql, query_name: name, binds: binds) { |env|
|
21
27
|
env.result = super env.sql, env.query_name, env.binds
|
@@ -17,6 +17,7 @@ module SchemaPlus
|
|
17
17
|
module Indexes
|
18
18
|
ENV = [:connection, :query_name, :table_name, :index_definitions]
|
19
19
|
end
|
20
|
+
|
20
21
|
end
|
21
22
|
|
22
23
|
module Migration
|
@@ -27,6 +28,11 @@ module SchemaPlus
|
|
27
28
|
module Index
|
28
29
|
ENV = [:caller, :operation, :table_name, :column_names, :options]
|
29
30
|
end
|
31
|
+
|
32
|
+
module DropTable
|
33
|
+
ENV = [:connection, :table_name, :options]
|
34
|
+
end
|
35
|
+
|
30
36
|
end
|
31
37
|
|
32
38
|
module Sql
|
data/spec/dumper_spec.rb
CHANGED
data/spec/middleware_spec.rb
CHANGED
@@ -59,6 +59,10 @@ describe SchemaMonkey::Middleware do
|
|
59
59
|
Then { expect_middleware(enable: {type: :reference}, env: {operation: :define, column_name: "ref_id"}) { table_statement(:belongs_to, "ref") } }
|
60
60
|
end
|
61
61
|
|
62
|
+
context TestReporter::Middleware::Migration::DropTable do
|
63
|
+
Then { expect_middleware { connection.drop_table "things" } }
|
64
|
+
end
|
65
|
+
|
62
66
|
context TestReporter::Middleware::Migration::Index do
|
63
67
|
Given { migration.add_column("things", "column1", "integer") }
|
64
68
|
Then { expect_middleware { table_statement(:index, "id") } }
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module TestReporter
|
2
|
-
|
2
|
+
|
3
3
|
class Called < Exception
|
4
4
|
attr_accessor :middleware, :env
|
5
5
|
def initialize(middleware:, env:)
|
@@ -32,6 +32,7 @@ module TestReporter
|
|
32
32
|
module Migration
|
33
33
|
module Column ; include Notify ; end
|
34
34
|
module Index ; include Notify ; end
|
35
|
+
module DropTable ; include Notify ; end
|
35
36
|
end
|
36
37
|
|
37
38
|
module Sql
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schema_plus_core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ronen barzel
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-02-
|
11
|
+
date: 2015-02-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|