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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f788d32a4cd252a5254b82928cab9b62b66cfd80e00dd010bdb901b60a6ddc6c
4
- data.tar.gz: 2c44d64d8f3225e809c113894ec77df2e64c6dae513a12065e171bd892c80c7d
3
+ metadata.gz: 82365b7ef365626c49c238c05258d85603f6d9cf18c080c5ef39329c1ecf3346
4
+ data.tar.gz: d1cf7a5a2f060ab68b1daf20a0495de5a09b706b272387810c3c4acce6e42470
5
5
  SHA512:
6
- metadata.gz: bbf514f03d362582bf15cd38301a9f2929c559ea702c283bae2c01720f48e3f613e93ce1580906983d567048d7241d70af92b94f54f4d992da06f3497c9afc8e
7
- data.tar.gz: 66200b79760795f6d3e622b4b29c13285fb042e4e168f1fea34ac38161bca41e4330a451add6f9a0db777dd20cbde97567cd687c811dff1675a1090a24dc7704
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 a line to `CHANGELOG.md` under `### Unreleased` describing your change
96
- from the point of view of someone using the gem. Leave `lib/with_model/version.rb`
97
- alone: version bumps and releases are cut separately by a maintainer, so a bump
98
- in a pull request only creates a conflict.
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`), or one whose table has no inheritance column to tell a
241
- subclass's rows apart. A name that resolves to nothing raises
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
@@ -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. `table(false)` takes no block.
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)
@@ -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
- stubber.unstub_const
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 inherit its
8
- # superclass's table instead of getting one of its own, as Rails Single Table
9
- # Inheritance requires. Selected by `table(false)`.
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}: both errors are permanent
38
- # facts about the superclass passed in, not about when it was looked at, so no
39
- # amount of waiting makes them work.
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
- raise_invalid_superclass "#{@superclass} has none to inherit" unless table_name?
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
- # Removes the rows this model wrote, which would otherwise outlive the
58
- # constant that names them and make the superclass unloadable
59
- # (`ActiveRecord::SubclassNotFound`).
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 unless klass.table_exists?
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 classes alike have no `table_name`, so
83
- # this is what "nothing to inherit" actually looks like - `abstract_class?`
84
- # is false for `ActiveRecord::Base` and so does not describe both.
85
- def table_name? = @superclass.table_name.present?
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
 
@@ -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.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module WithModel
4
- VERSION = "2.4.0"
4
+ VERSION = "2.5.0"
5
5
  end
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
@@ -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.0
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