-
-
Notifications
You must be signed in to change notification settings - Fork 372
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reject sequence definitions for Active Record primary keys
Alternative to [thoughtbot/factory_bot#1586][] Depends on [thoughtbot/factory_bot#1587][] First, introduce the `FactoryBotRails::FactoryValidator` class to serve as a generic `factory_bot.compile_factory` event observer. Throughout the lifecycle of the `FactoryBotRails::Railtie`, afford various initializers with opportunities to add purpose-built validators to the instance's internal set. When `factory_bot.compile_factory` events are published, iterate through the list of validators and forward along the value of the [event.payload][] to the validator's `#validate!` method. Next, introduce the Active Record-specific `FactoryBotRails::FactoryValidator::ActiveRecordValidator` class. Only require the module whenever [Active Record's engine is loaded][on_load]. The `ActiveRecordValidator#validate!` method rejects attributes that define primary key generation logic for `ActiveRecord::Base` descendants. In order to test this behavior, add a development dependency on `sqlite3` and `activerecord`, along with some model and database table generating helper methods. [thoughtbot/factory_bot#1586]: thoughtbot/factory_bot#1586 [thoughtbot/factory_bot#1587]: thoughtbot/factory_bot#1587 [event.payload]: module-ActiveSupport::Notifications-label-Subscribers [on_load]: https://guides.rubyonrails.org/engines.html#avoid-loading-rails-frameworks
- Loading branch information
1 parent
a28bec8
commit 5cc3531
Showing
8 changed files
with
157 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
module FactoryBotRails | ||
class FactoryValidator | ||
def initialize(validators = []) | ||
@validators = Array(validators) | ||
end | ||
|
||
def add_validator(validator) | ||
@validators << validator | ||
end | ||
|
||
def run | ||
ActiveSupport::Notifications.subscribe("factory_bot.compile_factory") do |event| | ||
@validators.each { |validator| validator.validate!(event.payload) } | ||
end | ||
end | ||
end | ||
end |
16 changes: 16 additions & 0 deletions
16
lib/factory_bot_rails/factory_validator/active_record_validator.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
module FactoryBotRails | ||
class FactoryValidator | ||
class ActiveRecordValidator | ||
def validate!(payload) | ||
attributes, for_class = payload.values_at(:attributes, :class) | ||
attributes.each do |attribute| | ||
if for_class < ActiveRecord::Base && for_class.primary_key == attribute.name.to_s | ||
raise FactoryBot::AttributeDefinitionError, <<~ERROR | ||
Attribute generates #{for_class.primary_key.inspect} primary key for #{for_class.name}" | ||
ERROR | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
24 changes: 24 additions & 0 deletions
24
spec/factory_bot_rails/factory_validator/active_record_validator_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# frozen_string_literal: true | ||
|
||
describe FactoryBotRails::FactoryValidator do | ||
before { FactoryBot.reload } | ||
|
||
describe "ActiveRecordValidator" do | ||
context "when defined with a class that descends from ActiveRecord::Base" do | ||
it "raises an error for a sequence generating its primary key" do | ||
define_model "Article", an_id: :integer do | ||
self.primary_key = :an_id | ||
end | ||
|
||
FactoryBot.define do | ||
factory :article do | ||
sequence(:an_id) | ||
end | ||
end | ||
|
||
expect { FactoryBot.create(:article) } | ||
.to raise_error(FactoryBot::AttributeDefinitionError) | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
require "active_record" | ||
|
||
module DefineConstantMacros | ||
def define_class(path, base = Object, &block) | ||
const = stub_const(path, Class.new(base)) | ||
const.class_eval(&block) if block | ||
const | ||
end | ||
|
||
def define_model(name, columns = {}, &block) | ||
model = define_class(name, ActiveRecord::Base, &block) | ||
create_table(model.table_name) do |table| | ||
columns.each do |column_name, type| | ||
table.column column_name, type | ||
end | ||
end | ||
model | ||
end | ||
|
||
def create_table(table_name, &block) | ||
connection = ActiveRecord::Base.connection | ||
|
||
begin | ||
connection.execute("DROP TABLE IF EXISTS #{table_name}") | ||
connection.create_table(table_name, &block) | ||
created_tables << table_name | ||
connection | ||
rescue Exception => e # rubocop:disable Lint/RescueException | ||
connection.execute("DROP TABLE IF EXISTS #{table_name}") | ||
raise e | ||
end | ||
end | ||
|
||
def clear_generated_tables | ||
created_tables.each do |table_name| | ||
clear_generated_table(table_name) | ||
end | ||
created_tables.clear | ||
end | ||
|
||
def clear_generated_table(table_name) | ||
ActiveRecord::Base | ||
.connection | ||
.execute("DROP TABLE IF EXISTS #{table_name}") | ||
end | ||
|
||
private | ||
|
||
def created_tables | ||
@created_tables ||= [] | ||
end | ||
end | ||
|
||
RSpec.configure do |config| | ||
config.include DefineConstantMacros | ||
|
||
config.before(:all) do | ||
ActiveRecord::Base.establish_connection( | ||
adapter: "sqlite3", | ||
database: ":memory:" | ||
) | ||
end | ||
|
||
config.after do | ||
clear_generated_tables | ||
end | ||
end |