-
-
Notifications
You must be signed in to change notification settings - Fork 373
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 (#419)
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 0040292
Showing
9 changed files
with
195 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
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,39 @@ | ||
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", &validate_compiled_factory) | ||
end | ||
|
||
private | ||
|
||
def validate_compiled_factory | ||
if Rails.version >= "6.0" | ||
rails_6_0_support | ||
else | ||
rails_5_2_support | ||
end | ||
end | ||
|
||
def rails_6_0_support | ||
proc do |event| | ||
@validators.each { |validator| validator.validate!(event.payload) } | ||
end | ||
end | ||
|
||
def rails_5_2_support | ||
proc do |*notification_event_arguments| | ||
event = ActiveSupport::Notifications::Event.new(*notification_event_arguments) | ||
|
||
rails_6_0_support.call(event) | ||
end | ||
end | ||
end | ||
end |
18 changes: 18 additions & 0 deletions
18
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,18 @@ | ||
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}" | ||
Do not define #{for_class.primary_key.inspect}. Instead, rely on the database to generate it. | ||
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,12 +3,15 @@ | |
require "factory_bot" | ||
require "factory_bot_rails/generator" | ||
require "factory_bot_rails/reloader" | ||
require "factory_bot_rails/factory_validator" | ||
require "rails" | ||
|
||
module FactoryBotRails | ||
class Railtie < Rails::Railtie | ||
config.factory_bot = ActiveSupport::OrderedOptions.new | ||
config.factory_bot.definition_file_paths = FactoryBot.definition_file_paths | ||
config.factory_bot.reject_primary_key_attributes = true | ||
config.factory_bot.validator = FactoryBotRails::FactoryValidator.new | ||
|
||
initializer "factory_bot.set_fixture_replacement" do | ||
Generator.new(config).run | ||
|
@@ -18,9 +21,20 @@ class Railtie < Rails::Railtie | |
FactoryBot.definition_file_paths = definition_file_paths | ||
end | ||
|
||
ActiveSupport.on_load :active_record do | ||
config = Rails.configuration.factory_bot | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
olleolleolle
Contributor
|
||
|
||
if config.reject_primary_key_attributes | ||
require "factory_bot_rails/factory_validator/active_record_validator" | ||
|
||
config.validator.add_validator FactoryValidator::ActiveRecordValidator.new | ||
end | ||
end | ||
|
||
config.after_initialize do |app| | ||
FactoryBot.find_definitions | ||
Reloader.new(app).run | ||
app.config.factory_bot.validator.run | ||
end | ||
|
||
private | ||
|
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 |
1 comment
on commit 0040292
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this planned to be released soon ?
This breaks for me with the following error: