with_model 2.4.0 → 2.5.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/CHANGELOG.md +7 -0
- data/CONTRIBUTING.md +22 -4
- data/README.md +61 -2
- data/lib/with_model/model/dsl.rb +3 -1
- data/lib/with_model/model.rb +18 -3
- data/lib/with_model/null_table.rb +49 -19
- data/lib/with_model/table.rb +6 -0
- data/lib/with_model/version.rb +1 -1
- data/spec/readme_spec.rb +20 -0
- data/spec/table_false_spec.rb +169 -0
- data/test/own_table_name_test.rb +81 -0
- data/test_unit/own_table_name_test.rb +81 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 82365b7ef365626c49c238c05258d85603f6d9cf18c080c5ef39329c1ecf3346
|
|
4
|
+
data.tar.gz: d1cf7a5a2f060ab68b1daf20a0495de5a09b706b272387810c3c4acce6e42470
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6c4d55dda6fe90872b8a31799207acfe4a910734f20e26cd870103d4ddee2dac6f7f756474f2982e944337ec06502b507622a95c89d400df64aae763554b04c7
|
|
7
|
+
data.tar.gz: f3fc56af09b5da30a0bde842ff0cc66d448089a638bce6203485ce9f48fe35014b21c05cb1d1f06fc179f49077c58f365837e2f5ab15534d0781a0f4f297c4d8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
### Unreleased
|
|
2
2
|
|
|
3
|
+
### 2.5.0
|
|
4
|
+
|
|
5
|
+
- Allow `table(false)` for an abstract model, and for a model whose superclass
|
|
6
|
+
is `ActiveRecord::Base` or abstract when the model has an existing table: the
|
|
7
|
+
one its model block names with `self.table_name`, or else the one Active
|
|
8
|
+
Record names after the model. Its rows are left for the suite to clean up.
|
|
9
|
+
|
|
3
10
|
### 2.4.0
|
|
4
11
|
|
|
5
12
|
- Add Test::Unit support, including lifecycle integration for dynamic models and
|
data/CONTRIBUTING.md
CHANGED
|
@@ -92,9 +92,27 @@ bundle exec standardrb --fix
|
|
|
92
92
|
|
|
93
93
|
## Changes and releases
|
|
94
94
|
|
|
95
|
-
Please add
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
Please add an entry to `CHANGELOG.md` under `### Unreleased` for each change
|
|
96
|
+
someone using the gem would notice. Leave `lib/with_model/version.rb` alone:
|
|
97
|
+
version bumps and releases are cut separately by a maintainer, so a bump in a
|
|
98
|
+
pull request only creates a conflict.
|
|
99
|
+
|
|
100
|
+
### Changelog format
|
|
101
|
+
|
|
102
|
+
- Each release has a `### X.Y.Z` heading, newest first, with unreleased changes
|
|
103
|
+
under `### Unreleased` at the top. Leave a blank line after each heading.
|
|
104
|
+
- Write one bullet per change, from the point of view of someone using the gem:
|
|
105
|
+
what they can now do or what behaves differently, not how the code changed.
|
|
106
|
+
- Start with a verb in the imperative mood (Add, Allow, Fix, Deprecate, Remove,
|
|
107
|
+
Require), and write a full sentence ending in a period.
|
|
108
|
+
- Put code, such as method names, options, and constants, in backticks.
|
|
109
|
+
- Wrap lines at 80 columns, indenting continuation lines by two spaces.
|
|
110
|
+
- List dependency changes last, Active Record before Ruby, with added support
|
|
111
|
+
before a raised minimum: "Add support for Ruby 4.0." then "Require Ruby 3.3
|
|
112
|
+
or later."
|
|
113
|
+
- Don't cite issue or pull request numbers. The pull request links the issue.
|
|
114
|
+
- Credit contributors from outside the maintainers by name, in parentheses at
|
|
115
|
+
the end of the entry, as in "Create table in same database as `superclass`
|
|
116
|
+
(John Doe)."
|
|
99
117
|
|
|
100
118
|
Last, but not least, have fun.
|
data/README.md
CHANGED
|
@@ -237,8 +237,9 @@ end
|
|
|
237
237
|
A `superclass:` that cannot be used raises `WithModel::InvalidSuperclass`: one
|
|
238
238
|
that is not an Active Record class, one with no table of its own
|
|
239
239
|
(`ActiveRecord::Base`, or an abstract class such as a Rails app's
|
|
240
|
-
`ApplicationRecord`)
|
|
241
|
-
|
|
240
|
+
`ApplicationRecord`) when the model has no existing table of its own either (see
|
|
241
|
+
[Existing tables](#existing-tables)), or a concrete one whose table has no
|
|
242
|
+
inheritance column to tell a subclass's rows apart. A name that resolves to nothing raises
|
|
242
243
|
`WithModel::MissingSuperclass`, a kind of `InvalidSuperclass`, quoting the
|
|
243
244
|
constant Ruby could not find — for a namespaced name, that is the segment which
|
|
244
245
|
is actually missing. Both are `ArgumentError`s.
|
|
@@ -249,6 +250,64 @@ enough on its own. Rows the model wrote are deleted when it goes away, since the
|
|
|
249
250
|
name a class that is about to stop existing and would make the superclass
|
|
250
251
|
unloadable.
|
|
251
252
|
|
|
253
|
+
### Existing tables
|
|
254
|
+
|
|
255
|
+
`table(false)` also suits a model that maps to a table the suite already has,
|
|
256
|
+
such as a legacy table or one that several models share. Set `self.table_name`
|
|
257
|
+
in the model block, or leave it out to use the table Active Record names after
|
|
258
|
+
the model (`phoenixes` for `Phoenix`):
|
|
259
|
+
|
|
260
|
+
```ruby
|
|
261
|
+
describe "with_model supports existing tables" do
|
|
262
|
+
with_table :legendary_creatures do |t|
|
|
263
|
+
t.string "name"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
with_model :Phoenix do
|
|
267
|
+
table(false)
|
|
268
|
+
|
|
269
|
+
model do
|
|
270
|
+
self.table_name = "legendary_creatures"
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it "uses the table it names" do
|
|
275
|
+
Phoenix.create!(name: "Fawkes")
|
|
276
|
+
|
|
277
|
+
expect(Phoenix.table_name).to eq "legendary_creatures"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
The table must exist before the model is set up, so declare `with_table` (or
|
|
283
|
+
whatever creates it) first. It belongs to the suite, not the model, so its rows
|
|
284
|
+
are left in place when the model goes away. Clean them up as you would the rest of that table, with
|
|
285
|
+
transactional tests or otherwise.
|
|
286
|
+
|
|
287
|
+
The superclass can be abstract, as a Rails app's `ApplicationRecord` is. An
|
|
288
|
+
abstract model needs no table, so declare it with `table(false)` as well:
|
|
289
|
+
|
|
290
|
+
```ruby
|
|
291
|
+
with_model :LegendaryCreature do
|
|
292
|
+
table(false)
|
|
293
|
+
|
|
294
|
+
model do
|
|
295
|
+
self.abstract_class = true
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
with_model :Phoenix, superclass: :LegendaryCreature do
|
|
300
|
+
table(false)
|
|
301
|
+
|
|
302
|
+
model do
|
|
303
|
+
self.table_name = "legendary_creatures"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Active Record never applies single table inheritance below an abstract class, so
|
|
309
|
+
an abstract superclass's table, if it has one, needs no inheritance column.
|
|
310
|
+
|
|
252
311
|
## Foreign keys
|
|
253
312
|
|
|
254
313
|
`foreign_key: true` asks Active Record to infer the table a foreign key points
|
data/lib/with_model/model/dsl.rb
CHANGED
|
@@ -13,7 +13,9 @@ module WithModel
|
|
|
13
13
|
#
|
|
14
14
|
# Pass `false` instead of options to create no table at all, so that the
|
|
15
15
|
# model inherits its superclass's table as Rails Single Table Inheritance
|
|
16
|
-
# requires
|
|
16
|
+
# requires, or uses an existing table: the one its model block names with
|
|
17
|
+
# `self.table_name`, or else the one Active Record names after the model.
|
|
18
|
+
# An abstract model needs no table at all. `table(false)` takes no block.
|
|
17
19
|
#
|
|
18
20
|
# @see https://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-create_table
|
|
19
21
|
def table(options = {}, &block)
|
data/lib/with_model/model.rb
CHANGED
|
@@ -73,9 +73,7 @@ module WithModel
|
|
|
73
73
|
# Before `unstub_const`: a teardown that identifies this model's own rows
|
|
74
74
|
# can only do so while the class still has its name.
|
|
75
75
|
table.teardown(@model)
|
|
76
|
-
|
|
77
|
-
WithModel::DescendantsTracker.clear([@model])
|
|
78
|
-
@model = nil
|
|
76
|
+
discard_model
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
private
|
|
@@ -127,9 +125,26 @@ module WithModel
|
|
|
127
125
|
def setup_model
|
|
128
126
|
table.configure(@model)
|
|
129
127
|
@model.class_eval(&@model_block) if @model_block
|
|
128
|
+
verify_model
|
|
130
129
|
@model.reset_column_information
|
|
131
130
|
end
|
|
132
131
|
|
|
132
|
+
# A model rejected here was never usable, so it is discarded at once rather
|
|
133
|
+
# than left for teardown: there are no rows to clean up, and a caller that
|
|
134
|
+
# does not tear down would otherwise be left with its constant.
|
|
135
|
+
def verify_model
|
|
136
|
+
table.verify(@model)
|
|
137
|
+
rescue InvalidSuperclass
|
|
138
|
+
discard_model
|
|
139
|
+
raise
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def discard_model
|
|
143
|
+
stubber.unstub_const
|
|
144
|
+
WithModel::DescendantsTracker.clear([@model])
|
|
145
|
+
@model = nil
|
|
146
|
+
end
|
|
147
|
+
|
|
133
148
|
def stubber
|
|
134
149
|
@stubber ||= ConstantStubber.new const_name
|
|
135
150
|
end
|
|
@@ -4,9 +4,11 @@ require "active_record"
|
|
|
4
4
|
require "with_model/invalid_superclass"
|
|
5
5
|
|
|
6
6
|
module WithModel
|
|
7
|
-
# Stands in for a {WithModel::Table} when a model should
|
|
8
|
-
#
|
|
9
|
-
# Inheritance requires
|
|
7
|
+
# Stands in for a {WithModel::Table} when a model should not get a table of its
|
|
8
|
+
# own: either it inherits its superclass's table, as Rails Single Table
|
|
9
|
+
# Inheritance requires, or it uses an existing table: one its model block
|
|
10
|
+
# names with `self.table_name`, or the one Active Record names after the
|
|
11
|
+
# model. An abstract model needs neither. Selected by `table(false)`.
|
|
10
12
|
#
|
|
11
13
|
# In general, direct use of this class should be avoided. Instead use
|
|
12
14
|
# either the {WithModel high-level API} or {WithModel::Model::DSL low-level API}.
|
|
@@ -21,7 +23,9 @@ module WithModel
|
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# Creates nothing, but raises {WithModel::InvalidSuperclass} when the
|
|
24
|
-
# superclass cannot support single table inheritance.
|
|
26
|
+
# superclass has a table that cannot support single table inheritance.
|
|
27
|
+
# Whether a superclass with nothing to inherit is a problem depends on the
|
|
28
|
+
# model block, so that waits for {#verify}.
|
|
25
29
|
#
|
|
26
30
|
# Errors describe the model's situation rather than the call that produced
|
|
27
31
|
# it. Any expression that evaluates to false selects a NullTable, so there
|
|
@@ -34,14 +38,13 @@ module WithModel
|
|
|
34
38
|
# `StatementInvalid` naming the table if it never appears, so raising here
|
|
35
39
|
# would only forbid working setups.
|
|
36
40
|
#
|
|
37
|
-
# What is left is {WithModel::InvalidSuperclass}:
|
|
38
|
-
#
|
|
39
|
-
#
|
|
41
|
+
# What is left is {WithModel::InvalidSuperclass}: a missing inheritance
|
|
42
|
+
# column, like a missing table to inherit, is a permanent fact about the
|
|
43
|
+
# superclass passed in, not about when it was looked at, so no amount of
|
|
44
|
+
# waiting makes it work.
|
|
40
45
|
def create
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
# Nothing more can be checked until there is a table to look at.
|
|
44
|
-
return unless table_exists?
|
|
46
|
+
# Nothing can be checked until there is a table to look at.
|
|
47
|
+
return unless inheritable_table? && table_exists?
|
|
45
48
|
return if inheritance_column?
|
|
46
49
|
|
|
47
50
|
raise_invalid_superclass "#{@superclass}'s table #{@superclass.table_name.inspect} has no " \
|
|
@@ -54,9 +57,33 @@ module WithModel
|
|
|
54
57
|
def configure(klass)
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
+
# Raises {WithModel::InvalidSuperclass} when the superclass has no table to
|
|
61
|
+
# inherit (`ActiveRecord::Base`, or an abstract class) and the model's own
|
|
62
|
+
# table - the one its block named, or else the one Active Record names after
|
|
63
|
+
# the model - does not exist once its block has run. A model its block made
|
|
64
|
+
# abstract needs no table, so it always passes.
|
|
65
|
+
#
|
|
66
|
+
# Unlike a superclass's missing table, which {#create} lets through, this
|
|
67
|
+
# table must already exist. A model with nothing to inherit is far more
|
|
68
|
+
# likely a mistaken attempt at single table inheritance than a deliberate
|
|
69
|
+
# reference to a table that is yet to be created, and with nothing to point
|
|
70
|
+
# at, Active Record's own error would leave that mistake unexplained.
|
|
71
|
+
def verify(klass)
|
|
72
|
+
return if klass.abstract_class? || inheritable_table? || own_table_exists?(klass)
|
|
73
|
+
|
|
74
|
+
raise_invalid_superclass "#{@superclass} has none to inherit and there is no " \
|
|
75
|
+
"#{klass.table_name.inspect} table. To use an existing table, create it before " \
|
|
76
|
+
"the model or set `self.table_name` in its model block."
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Removes the rows a single table inheritance subclass wrote, which would
|
|
80
|
+
# otherwise outlive the constant that names them and make the superclass
|
|
81
|
+
# unloadable (`ActiveRecord::SubclassNotFound`).
|
|
82
|
+
#
|
|
83
|
+
# A model that is its own base class names a table that belongs to the
|
|
84
|
+
# suite, and its rows name no class, so they are left for the suite to clean
|
|
85
|
+
# up like the rest of that table - its table need not have an inheritance
|
|
86
|
+
# column to delete by, either.
|
|
60
87
|
#
|
|
61
88
|
# `unscoped` is required because a `default_scope` on the superclass would
|
|
62
89
|
# otherwise hide rows from cleanup; the inheritance-column condition is
|
|
@@ -66,7 +93,7 @@ module WithModel
|
|
|
66
93
|
# A table that does not exist holds no rows to remove, and failing here
|
|
67
94
|
# would fail an example whose body had already passed.
|
|
68
95
|
def teardown(klass)
|
|
69
|
-
return
|
|
96
|
+
return if klass.base_class? || !klass.table_exists?
|
|
70
97
|
|
|
71
98
|
klass.unscoped.where(klass.inheritance_column => klass.sti_name).each(&:destroy!)
|
|
72
99
|
end
|
|
@@ -79,10 +106,13 @@ module WithModel
|
|
|
79
106
|
|
|
80
107
|
def inheritance_column = @superclass.inheritance_column
|
|
81
108
|
|
|
82
|
-
# `ActiveRecord::Base` and abstract
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
|
|
109
|
+
# `ActiveRecord::Base` has no `table_name`, and an abstract class has none to
|
|
110
|
+
# pass on through single table inheritance even when it has one: Active
|
|
111
|
+
# Record makes each of its subclasses a base class of its own. Neither test
|
|
112
|
+
# covers both, since `abstract_class?` is false for `ActiveRecord::Base`.
|
|
113
|
+
def inheritable_table? = !@superclass.abstract_class? && @superclass.table_name.present?
|
|
114
|
+
|
|
115
|
+
def own_table_exists?(klass) = klass.connection.data_source_exists?(klass.table_name)
|
|
86
116
|
|
|
87
117
|
def table_exists? = @superclass.table_exists?
|
|
88
118
|
|
data/lib/with_model/table.rb
CHANGED
|
@@ -30,6 +30,12 @@ module WithModel
|
|
|
30
30
|
klass.table_name = @name
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
# Accepts any model block: the table is the model's own. Present so that
|
|
34
|
+
# {WithModel::NullTable} - which checks the model once its block has run -
|
|
35
|
+
# can stand in here.
|
|
36
|
+
def verify(_klass)
|
|
37
|
+
end
|
|
38
|
+
|
|
33
39
|
# Removes everything this table holds by dropping it. The model is not
|
|
34
40
|
# needed, but is accepted so that {WithModel::NullTable} - which does need
|
|
35
41
|
# it - can stand in here.
|
data/lib/with_model/version.rb
CHANGED
data/spec/readme_spec.rb
CHANGED
|
@@ -145,6 +145,26 @@ describe "with_model supports Single Table Inheritance" do
|
|
|
145
145
|
end
|
|
146
146
|
end
|
|
147
147
|
|
|
148
|
+
describe "with_model supports existing tables" do
|
|
149
|
+
with_table :legendary_creatures do |t|
|
|
150
|
+
t.string "name"
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
with_model :Phoenix do
|
|
154
|
+
table(false)
|
|
155
|
+
|
|
156
|
+
model do
|
|
157
|
+
self.table_name = "legendary_creatures"
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "uses the table it names" do
|
|
162
|
+
Phoenix.create!(name: "Fawkes")
|
|
163
|
+
|
|
164
|
+
expect(Phoenix.table_name).to eq "legendary_creatures"
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
148
168
|
describe "with_model supports foreign keys" do
|
|
149
169
|
with_model :Author do
|
|
150
170
|
table
|
data/spec/table_false_spec.rb
CHANGED
|
@@ -433,6 +433,135 @@ RSpec.describe "table(false)" do
|
|
|
433
433
|
end
|
|
434
434
|
end
|
|
435
435
|
|
|
436
|
+
# Not single table inheritance at all: the model is its own base class and
|
|
437
|
+
# names an existing table, such as a legacy table or one several models share.
|
|
438
|
+
describe "with a model that sets its own table_name" do
|
|
439
|
+
with_table :legendary_creatures do |t|
|
|
440
|
+
t.string :name
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Declared between the two so that it runs after the model is torn down but
|
|
444
|
+
# before the table is dropped.
|
|
445
|
+
after do
|
|
446
|
+
expect(ActiveRecord::Base.connection.select_value("SELECT COUNT(*) FROM legendary_creatures"))
|
|
447
|
+
.to eq @rows_written.to_i
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
with_model :Phoenix do
|
|
451
|
+
table(false)
|
|
452
|
+
model do
|
|
453
|
+
self.table_name = "legendary_creatures"
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
it "uses the table it names" do
|
|
458
|
+
Phoenix.create!(name: "Fawkes")
|
|
459
|
+
@rows_written = 1
|
|
460
|
+
|
|
461
|
+
expect(Phoenix.table_name).to eq "legendary_creatures"
|
|
462
|
+
expect(Phoenix.count).to eq 1
|
|
463
|
+
end
|
|
464
|
+
|
|
465
|
+
it "creates no table of its own" do
|
|
466
|
+
expect(ActiveRecord::Base.connection.tables.grep(/with_model_phoenix/)).to be_empty
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it "is its own base class" do
|
|
470
|
+
expect(Phoenix.base_class).to eq Phoenix
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
# The table belongs to the suite, which is left to clean it up - with
|
|
474
|
+
# transactional tests, or however it cleans up its other tables. Deleting by
|
|
475
|
+
# type would fail outright here, since there is no type column.
|
|
476
|
+
it "leaves its rows alone when torn down" do
|
|
477
|
+
Phoenix.create!(name: "Fawkes")
|
|
478
|
+
Phoenix.create!(name: "Buckbeak")
|
|
479
|
+
@rows_written = 2
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
# An abstract model needs no table, so `table(false)` suits it too.
|
|
483
|
+
context "with an abstract superclass that has no table" do
|
|
484
|
+
with_model :ApplicationRecord do
|
|
485
|
+
table(false)
|
|
486
|
+
model do
|
|
487
|
+
self.abstract_class = true
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
with_model :Griffin, superclass: :ApplicationRecord do
|
|
492
|
+
table(false)
|
|
493
|
+
model do
|
|
494
|
+
self.table_name = "legendary_creatures"
|
|
495
|
+
end
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
it "uses the table it names" do
|
|
499
|
+
Griffin.create!(name: "Buckbeak")
|
|
500
|
+
@rows_written = 1
|
|
501
|
+
|
|
502
|
+
expect(Griffin.table_name).to eq "legendary_creatures"
|
|
503
|
+
expect(Griffin.count).to eq 1
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
it "leaves the abstract superclass without a table" do
|
|
507
|
+
expect(ApplicationRecord.table_name).to be_nil
|
|
508
|
+
end
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Active Record never applies single table inheritance below an abstract
|
|
512
|
+
# class, so its table needs no inheritance column.
|
|
513
|
+
context "with an abstract superclass that has a table" do
|
|
514
|
+
with_model :LegendaryCreature do
|
|
515
|
+
table
|
|
516
|
+
model do
|
|
517
|
+
self.abstract_class = true
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
with_model :Sphinx, superclass: :LegendaryCreature do
|
|
522
|
+
table(false)
|
|
523
|
+
model do
|
|
524
|
+
self.table_name = "legendary_creatures"
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
it "uses the table it names" do
|
|
529
|
+
Sphinx.create!(name: "Phix")
|
|
530
|
+
@rows_written = 1
|
|
531
|
+
|
|
532
|
+
expect(Sphinx.table_name).to eq "legendary_creatures"
|
|
533
|
+
expect(Sphinx.count).to eq 1
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
it "is its own base class" do
|
|
537
|
+
expect(Sphinx.base_class).to eq Sphinx
|
|
538
|
+
end
|
|
539
|
+
end
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Active Record's naming convention picks the table, so the model block need
|
|
543
|
+
# not name it.
|
|
544
|
+
describe "with a model named after an existing table" do
|
|
545
|
+
with_table :dragons do |t|
|
|
546
|
+
t.string :name
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
with_model :Dragon do
|
|
550
|
+
table(false)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
it "uses that table" do
|
|
554
|
+
Dragon.create!(name: "Norbert")
|
|
555
|
+
|
|
556
|
+
expect(Dragon.table_name).to eq "dragons"
|
|
557
|
+
expect(Dragon.count).to eq 1
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
it "creates no table of its own" do
|
|
561
|
+
expect(ActiveRecord::Base.connection.tables.grep(/with_model_dragon/)).to be_empty
|
|
562
|
+
end
|
|
563
|
+
end
|
|
564
|
+
|
|
436
565
|
describe "invalid configurations" do
|
|
437
566
|
# Creates immediately the same tableless subclass normally generated by:
|
|
438
567
|
#
|
|
@@ -461,6 +590,46 @@ RSpec.describe "table(false)" do
|
|
|
461
590
|
.to raise_error(WithModel::InvalidSuperclass, /ActiveRecord::Base has none to inherit/)
|
|
462
591
|
end
|
|
463
592
|
|
|
593
|
+
it "names the table it looked for" do
|
|
594
|
+
expect { create_tableless_subclass(superclass: ActiveRecord::Base) }
|
|
595
|
+
.to raise_error(WithModel::InvalidSuperclass, /there is no "tableless_subclasses" table/)
|
|
596
|
+
end
|
|
597
|
+
|
|
598
|
+
it "suggests creating that table first or naming another" do
|
|
599
|
+
expect { create_tableless_subclass(superclass: ActiveRecord::Base) }
|
|
600
|
+
.to raise_error(
|
|
601
|
+
WithModel::InvalidSuperclass,
|
|
602
|
+
/create it before the model or set `self.table_name` in its model block/
|
|
603
|
+
)
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
# Builds the model by hand so that, should the check let it through, the
|
|
607
|
+
# example can still destroy it rather than leak its constant.
|
|
608
|
+
it "looks for the table the model block names" do
|
|
609
|
+
subclass = WithModel::Model.new(:TablelessSubclass, superclass: ActiveRecord::Base)
|
|
610
|
+
WithModel::Model::DSL.new(subclass).instance_eval do
|
|
611
|
+
table(false)
|
|
612
|
+
model { self.table_name = "missing_furniture" }
|
|
613
|
+
end
|
|
614
|
+
|
|
615
|
+
begin
|
|
616
|
+
expect { subclass.create }
|
|
617
|
+
.to raise_error(WithModel::InvalidSuperclass, /there is no "missing_furniture" table/)
|
|
618
|
+
ensure
|
|
619
|
+
subclass.destroy if Object.const_defined?(:TablelessSubclass, false)
|
|
620
|
+
end
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
# The check needs the model block to have run, so the constant already
|
|
624
|
+
# exists when it fails. A test runner tears down after a failed setup, but
|
|
625
|
+
# nothing else does, and a leftover constant would leak into later tests.
|
|
626
|
+
it "leaves no constant behind" do
|
|
627
|
+
expect { create_tableless_subclass(superclass: ActiveRecord::Base) }
|
|
628
|
+
.to raise_error(WithModel::InvalidSuperclass)
|
|
629
|
+
|
|
630
|
+
expect(Object.const_defined?(:TablelessSubclass, false)).to be false
|
|
631
|
+
end
|
|
632
|
+
|
|
464
633
|
context "with an abstract superclass" do
|
|
465
634
|
with_model :TablelessSuperclass do
|
|
466
635
|
table
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_helper"
|
|
4
|
+
|
|
5
|
+
# A `table(false)` model that is its own base class and names an existing table
|
|
6
|
+
# rather than inheriting one. `with_table` is declared first, so the table is
|
|
7
|
+
# created before the model and dropped after it.
|
|
8
|
+
class OwnTableNameTest < Minitest::Test
|
|
9
|
+
with_table :mythical_beasts do |t|
|
|
10
|
+
t.string "name"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
with_model :Hippogriff do
|
|
14
|
+
table(false)
|
|
15
|
+
|
|
16
|
+
model do
|
|
17
|
+
self.table_name = "mythical_beasts"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_the_model_uses_the_table_it_names
|
|
22
|
+
Hippogriff.create!(name: "Buckbeak")
|
|
23
|
+
|
|
24
|
+
assert_equal "mythical_beasts", Hippogriff.table_name
|
|
25
|
+
assert_equal 1, Hippogriff.count
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_the_model_creates_no_table_of_its_own
|
|
29
|
+
assert_empty ActiveRecord::Base.connection.tables.grep(/with_model_hippogriff/)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A `table(false)` model that is its own base class and uses the table Active
|
|
34
|
+
# Record names after it, without naming it in the model block.
|
|
35
|
+
class ConventionalTableNameTest < Minitest::Test
|
|
36
|
+
with_table :unicorns do |t|
|
|
37
|
+
t.string "name"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
with_model :Unicorn do
|
|
41
|
+
table(false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_the_model_uses_the_table_named_after_it
|
|
45
|
+
Unicorn.create!(name: "Charlie")
|
|
46
|
+
|
|
47
|
+
assert_equal "unicorns", Unicorn.table_name
|
|
48
|
+
assert_equal 1, Unicorn.count
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A `table(false)` model whose superclass is abstract and, needing no table, is
|
|
53
|
+
# declared with `table(false)` too.
|
|
54
|
+
class AbstractSuperclassTableNameTest < Minitest::Test
|
|
55
|
+
with_table :winged_horses do |t|
|
|
56
|
+
t.string "name"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
with_model :FlyingCreature do
|
|
60
|
+
table(false)
|
|
61
|
+
|
|
62
|
+
model do
|
|
63
|
+
self.abstract_class = true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
with_model :Pegasus, superclass: :FlyingCreature do
|
|
68
|
+
table(false)
|
|
69
|
+
|
|
70
|
+
model do
|
|
71
|
+
self.table_name = "winged_horses"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_the_model_uses_the_table_it_names
|
|
76
|
+
Pegasus.create!(name: "Bellerophon")
|
|
77
|
+
|
|
78
|
+
assert_equal "winged_horses", Pegasus.table_name
|
|
79
|
+
assert_equal 1, Pegasus.count
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "test_unit_helper"
|
|
4
|
+
|
|
5
|
+
# A `table(false)` model that is its own base class and names an existing table
|
|
6
|
+
# rather than inheriting one. `with_table` is declared first, so the table is
|
|
7
|
+
# created before the model and dropped after it.
|
|
8
|
+
class TestUnitOwnTableNameTest < Test::Unit::TestCase
|
|
9
|
+
with_table :test_unit_mythical_beasts do |t|
|
|
10
|
+
t.string "name"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
with_model :TestUnitHippogriff do
|
|
14
|
+
table(false)
|
|
15
|
+
|
|
16
|
+
model do
|
|
17
|
+
self.table_name = "test_unit_mythical_beasts"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_the_model_uses_the_table_it_names
|
|
22
|
+
TestUnitHippogriff.create!(name: "Buckbeak")
|
|
23
|
+
|
|
24
|
+
assert_equal "test_unit_mythical_beasts", TestUnitHippogriff.table_name
|
|
25
|
+
assert_equal 1, TestUnitHippogriff.count
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_the_model_creates_no_table_of_its_own
|
|
29
|
+
assert_empty ActiveRecord::Base.connection.tables.grep(/with_model_test_unit_hippogriff/)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# A `table(false)` model that is its own base class and uses the table Active
|
|
34
|
+
# Record names after it, without naming it in the model block.
|
|
35
|
+
class TestUnitConventionalTableNameTest < Test::Unit::TestCase
|
|
36
|
+
with_table :test_unit_unicorns do |t|
|
|
37
|
+
t.string "name"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
with_model :TestUnitUnicorn do
|
|
41
|
+
table(false)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def test_the_model_uses_the_table_named_after_it
|
|
45
|
+
TestUnitUnicorn.create!(name: "Charlie")
|
|
46
|
+
|
|
47
|
+
assert_equal "test_unit_unicorns", TestUnitUnicorn.table_name
|
|
48
|
+
assert_equal 1, TestUnitUnicorn.count
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# A `table(false)` model whose superclass is abstract and, needing no table, is
|
|
53
|
+
# declared with `table(false)` too.
|
|
54
|
+
class TestUnitAbstractSuperclassTableNameTest < Test::Unit::TestCase
|
|
55
|
+
with_table :test_unit_winged_horses do |t|
|
|
56
|
+
t.string "name"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
with_model :TestUnitFlyingCreature do
|
|
60
|
+
table(false)
|
|
61
|
+
|
|
62
|
+
model do
|
|
63
|
+
self.abstract_class = true
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
with_model :TestUnitPegasus, superclass: :TestUnitFlyingCreature do
|
|
68
|
+
table(false)
|
|
69
|
+
|
|
70
|
+
model do
|
|
71
|
+
self.table_name = "test_unit_winged_horses"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def test_the_model_uses_the_table_it_names
|
|
76
|
+
TestUnitPegasus.create!(name: "Bellerophon")
|
|
77
|
+
|
|
78
|
+
assert_equal "test_unit_winged_horses", TestUnitPegasus.table_name
|
|
79
|
+
assert_equal 1, TestUnitPegasus.count
|
|
80
|
+
end
|
|
81
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: with_model
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Case Commons, LLC
|
|
@@ -75,11 +75,13 @@ files:
|
|
|
75
75
|
- spec/table_spec.rb
|
|
76
76
|
- spec/with_model_spec.rb
|
|
77
77
|
- test/declaration_order_test.rb
|
|
78
|
+
- test/own_table_name_test.rb
|
|
78
79
|
- test/spec_dsl_test.rb
|
|
79
80
|
- test/sti_test.rb
|
|
80
81
|
- test/test_helper.rb
|
|
81
82
|
- test/with_model_test.rb
|
|
82
83
|
- test_unit/lifecycle_test.rb
|
|
84
|
+
- test_unit/own_table_name_test.rb
|
|
83
85
|
- test_unit/sti_test.rb
|
|
84
86
|
- test_unit/test_unit_helper.rb
|
|
85
87
|
- test_unit/with_model_test.rb
|