Specs are long to run because of truncation of tables - ruby-on-rails

I have a Rails 4 app with simple specs. When I run a spec, it takes 26 seconds. If I look at the log, I can see this :
(38.5ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE .............# tons of tables
(10.3ms) SELECT schemaname || '.' || tablename
FROM pg_tables
WHERE
tablename !~ '_prt_' AND
tablename <> 'schema_migrations' AND
schemaname = ANY (current_schemas(false))
(2.3ms) select table_name from information_schema.views where table_schema = 'lap-test'
(10708.2ms) TRUNCATE TABLE "public"."bounded_facets_service_boutiques", "public"."versions", ..... # Tons of tables
(10823.0ms) TRUNCATE TABLE "public"."bounded_facets_service_boutiques", "public"."versions", .... #Tons of tables
(26.2ms) ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "bounded_facets_service_boutiques" ENABLE TRIGGER ALL;ALTER TABLE "versions" .... #Tons of tables
(0.6ms) BEGIN
(0.3ms) COMMIT
(0.2ms) BEGIN
(0.2ms) SAVEPOINT active_record_1
(0.2ms) RELEASE SAVEPOINT active_record_1
(0.2ms) SAVEPOINT active_record_1
(0.5ms) SAVEPOINT active_record_2
Administrator Exists (1.9ms) SELECT 1 AS one FROM "administrators" WHERE "administrators"."email" = 'person1#labandprocess.com' LIMIT 1
SQL (2.3ms) INSERT INTO "administrators" .....
(0.6ms) RELEASE SAVEPOINT active_record_2
(0.4ms) ROLLBACK TO SAVEPOINT active_record_1
(0.6ms) ROLLBACK
As you can see, The truncation takes 10 seconds 2 times.
I use database cleaner with this :
RSpec.configure do |config|
config.include Devise::TestHelpers, type: :controller
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
end
I tested with other strategies without result.
There is a lot of seeds in development mod but it's on another database. There is 107 models in this project.
Is there a solution ?

Related

Rails: Renaming column in migration fails

I have a simple migration which fails: The database is not affected, it still contains the old column name after migrating.
class RenameTypeToLicenseTypeInLicenses < ActiveRecord::Migration[5.1]
def change
rename_column :licenses, :type, :license_type
end
end
Renaming type to license_type is necessary because of this error:
The single-table inheritance mechanism failed to locate the subclass: 'creative commons'. This error is raised because the column 'type' is reserved for storing the class in case of inheritance. Please rename this column if you didn't intend it to be used for storing the inheritance class or overwrite License.inheritance_column to use another column for that information.
This is the output of rails db:migrate
igrating to RenameTypeToLicenseTypeInLicenses (20210422110849)
(0.2ms) BEGIN
== 20210422110849 RenameTypeToLicenseTypeInLicenses: migrating ================
== 20210422110849 RenameTypeToLicenseTypeInLicenses: migrated (0.0000s) =======
SQL (0.9ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20210422110849"]]
(1.4ms) COMMIT
ActiveRecord::InternalMetadata Load (0.5ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]]
(0.3ms) BEGIN
(0.3ms) COMMIT
(0.4ms) SELECT pg_advisory_unlock(2195010657070977375)
(0.6ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
How can I rename the type column?
edit:
As there's only little data in the table, I tried another approach:
remove_column :licenses, :type, :string
add_column :licenses, :license_type, :string
It doesn't work either. Output is
[NAME COLLISION] `type` is a reserved key in LicenseResource.
(0.7ms) SELECT pg_try_advisory_lock(2195010657070977375);
(1.3ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Migrating to ChangeTypeToLicenseTypeFromLicenses (20210422122638)
(0.5ms) BEGIN
== 20210422122638 ChangeTypeToLicenseTypeFromLicenses: migrating ==============
== 20210422122638 ChangeTypeToLicenseTypeFromLicenses: migrated (0.0000s) =====
SQL (1.0ms) INSERT INTO "schema_migrations" ("version") VALUES ($1) RETURNING "version" [["version", "20210422122638"]]
(4.6ms) COMMIT
ActiveRecord::InternalMetadata Load (0.4ms) SELECT "ar_internal_metadata".* FROM "ar_internal_metadata" WHERE "ar_internal_metadata"."key" = $1 LIMIT $2 [["key", "environment"], ["LIMIT", 1]]
(0.4ms) BEGIN
(0.4ms) COMMIT
(0.6ms) SELECT pg_advisory_unlock(2195010657070977375)
(0.9ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
Yeah, I wonder if ActiveRecord's migration code might be trying its best to keep type intact, on the assumption that it's currently being used for single table inheritance; it's oblivious to the fact you want to change it because it currently isn't 😊
There may be an idiomatic Rails way of telling the migration that what you're doing is fine, but to be honest I'd be tempted to drop down to the SQL level, thus bypassing Rails' erroneous protection:
class RenameTypeToLicenseTypeInLicenses < ActiveRecord::Migration[5.1]
def up
execute <<~SQL
ALTER TABLE licenses RENAME COLUMN type TO license_type;
SQL
end
def down
execute <<~SQL
ALTER TABLE licenses RENAME COLUMN license_type TO type;
SQL
end
end
As you're not using migration commands, you have to provide separate up/down code for both directions of the migration.
I believe this answer should help you: issue with column name 'type' in rails 3
type is one of those reserved words we shouldn't use. Check other reserved words here: https://riptutorial.com/ruby-on-rails/example/32446/reserved-word-list

RSpec + Poltergeist fails when setting authenticity_token

I am trying to test a feature in my Rails app using RSpec + Polgergeist.
My feature spec is very basic right now:
RSpec.feature 'Listing owners can create new listings' do
let(:owner) { create(:user, :owner) }
before do
login_as owner
# I've also tried this with as recommended by Devise when using Capybara-Webkit:
# login_as(user, scope: :user, run_callbacks: false)
visit new_listing_path
end
scenario 'successfully', js: true do
fill_in 'Field 1', with: 'Example'
click_link 'Create'
# expectations here...
end
end
I have the following in my rails_helper.rb
require 'capybara/poltergeist'
RSpec.configure do |config|
# ...
config.include Warden::Test::Helpers, type: :feature
config.after(type: :feature) { Warden.test_reset! }
# ...
end
and in a support/poltergeist.rb file
RSpec.configure do |config|
Capybara.javascript_driver = :poltergeist
# Increase timeout in case asset compilation
# takes longer than anticipated.
Capybara.register_driver :poltergeist do |app|
Capybara::Poltergeist::Driver.new(app, {timeout: 60})
end
end
The precise location that is failing is this, where I set the CSRF token using Vue.js:
import Vue from 'vue/dist/vue.esm'
import VueResource from 'vue-resource'
Vue.use(VueResource)
document.addEventListener('DOMContentLoaded', () => {
if(document.getElementById('listing-multistep') !== null) {
Vue.http.headers.common['X-CSRF-Token'] = document.querySelector('input[name="authenticity_token"]').getAttribute('value');
var listingForm = document.getElementById('listing_form');
var listing = JSON.parse(listingForm.dataset.listing);
// do magic....
}
});
The authenticity token is set from the Rails form partial, _form.html.erb:
<%= form_for(
listing,
html: { multipart: true, id: 'listing_form',
data: {
listing: listing.to_json(except: [:user_id, :created_at, :updated_at])
}
}) do |f| %>
<fieldset class="listing-step" v-show="activeStep === 0">
<%= render 'listings/form/basics', f: f, listing: listing %>
</fieldset>
<fieldset class="listing-step" v-show="activeStep === 1">
<%= render 'listings/form/location', f: f, listing: listing %>
</fieldset>
<fieldset class="listing-step" v-show="activeStep === 2">
<%= render 'listings/form/amenities', f: f, listing: listing %>
</fieldset>
<fieldset class="listing-step" v-show="activeStep === 3">
<%= render 'listings/form/description', f: f, listing: listing %>
</fieldset>
<% end %>
Setting the CSRF token this way seems to work in development, but when I run the spec, I am seeing the following error:
Failure/Error: visit new_listing_path
Capybara::Poltergeist::JavascriptError:
One or more errors were raised in the Javascript code on the page. If you don't care about these errors, you can ignore them by setting js_errors: false in your Poltergeist configuration (see documentation for details).
TypeError: null is not an object (evaluating 'document.querySelector('input[name="authenticity_token"]').getAttribute')
TypeError: null is not an object (evaluating 'document.querySelector('input[name="authenticity_token"]').getAttribute')
at http://127.0.0.1:54450/packs-test/dashboard-ca7eeed2cc4b22399ade.js:13086
All I can think of is that poltergeist isn't waiting for the DOM to load fully, but that would be strange because I have explicitly written the vue code within document.addEventListener('DOMContentLoaded', () => {
As an aside, I also have a support/database_cleaner.rb file with the following contents:
RSpec.configure do |conf|
conf.before(:suite) do
DatabaseCleaner.clean_with :truncation
end
conf.before(:each) do
DatabaseCleaner.strategy = :transaction
end
conf.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
conf.before(:each) do
DatabaseCleaner.start
end
conf.after(:each) do
DatabaseCleaner.clean
end
end
Any idea what may be causing the aforementioned error? Any help is much appreciated!
Thanks in advance
UPDATE
Here is a copy of the test.log file that is generated when I run the spec:
(1.3ms) SELECT "schema_migrations"."version" FROM "schema_migrations" ORDER BY "schema_migrations"."version" ASC
(0.2ms) BEGIN
(2.1ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" DISABLE TRIGGER ALL;ALTER TABLE "subscriptions" DISABLE TRIGGER ALL;ALTER TABLE "listings" DISABLE TRIGGER ALL;ALTER TABLE "advertisements" DISABLE TRIGGER ALL;ALTER TABLE "chatroom_users" DISABLE TRIGGER ALL;ALTER TABLE "chatrooms" DISABLE TRIGGER ALL;ALTER TABLE "messages" DISABLE TRIGGER ALL;ALTER TABLE "pictures" DISABLE TRIGGER ALL;ALTER TABLE "friendly_id_slugs" DISABLE TRIGGER ALL;ALTER TABLE "contact_messages" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
(0.7ms) COMMIT
(3.9ms) SELECT schemaname || '.' || tablename
FROM pg_tables
WHERE
tablename !~ '_prt_' AND
tablename <> 'schema_migrations' AND tablename <> 'ar_internal_metadata' AND
schemaname = ANY (current_schemas(false))
(2.5ms) select table_name from information_schema.views where table_schema = 'salayo_test'
(48.9ms) TRUNCATE TABLE "public"."subscriptions", "public"."listings", "public"."advertisements", "public"."chatroom_users", "public"."chatrooms", "public"."messages", "public"."pictures", "public"."friendly_id_slugs", "public"."contact_messages", "public"."users" RESTART IDENTITY CASCADE;
(1.3ms) BEGIN
(1.7ms) ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" ENABLE TRIGGER ALL;ALTER TABLE "subscriptions" ENABLE TRIGGER ALL;ALTER TABLE "listings" ENABLE TRIGGER ALL;ALTER TABLE "advertisements" ENABLE TRIGGER ALL;ALTER TABLE "chatroom_users" ENABLE TRIGGER ALL;ALTER TABLE "chatrooms" ENABLE TRIGGER ALL;ALTER TABLE "messages" ENABLE TRIGGER ALL;ALTER TABLE "pictures" ENABLE TRIGGER ALL;ALTER TABLE "friendly_id_slugs" ENABLE TRIGGER ALL;ALTER TABLE "contact_messages" ENABLE TRIGGER ALL;ALTER TABLE "users" ENABLE TRIGGER ALL
(0.6ms) COMMIT
(0.3ms) BEGIN
User Exists (2.0ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "dianna_kilback#okeefemccullough.org"], ["LIMIT", 1]]
SQL (1.1ms) INSERT INTO "users" ("first_name", "last_name", "email", "encrypted_password", "created_at", "updated_at", "role") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["first_name", "Hermann"], ["last_name", "VonRueden"], ["email", "dianna_kilback#okeefemccullough.org"], ["encrypted_password", "$2a$04$sSAdYthaHRhqjUqaing/weltoDcyHyR5Fyr2Pw/8xOtvz4mMPwWYm"], ["created_at", "2018-04-01 05:32:07.693349"], ["updated_at", "2018-04-01 05:32:07.693349"], ["role", 2]]
(0.6ms) COMMIT
Started GET "/en/listings/new" for 127.0.0.1 at 2018-04-01 14:32:09 +0900
(0.2ms) BEGIN
SQL (2.5ms) UPDATE "users" SET "sign_in_count" = $1, "current_sign_in_at" = $2, "last_sign_in_at" = $3, "current_sign_in_ip" = $4, "last_sign_in_ip" = $5, "updated_at" = $6 WHERE "users"."id" = $7 [["sign_in_count", 1], ["current_sign_in_at", "2018-04-01 05:32:09.833321"], ["last_sign_in_at", "2018-04-01 05:32:09.833321"], ["current_sign_in_ip", "127.0.0.1/32"], ["last_sign_in_ip", "127.0.0.1/32"], ["updated_at", "2018-04-01 05:32:09.866550"], ["id", 1]]
(0.6ms) COMMIT
Processing by ListingsController#new as HTML
Parameters: {"locale"=>"en"}
Rendering listings/new.html.erb within layouts/dashboard
Rendered listings/_prev_next_buttons.html.erb (3.6ms)
Rendered listings/form/_basics.html.erb (30.6ms)
Rendered listings/form/_location.html.erb (2.9ms)
Rendered listings/form/_amenities.html.erb (0.5ms)
Rendered listings/form/_description.html.erb (3.5ms)
Rendered listings/_form.html.erb (79.4ms)
Rendered listings/new.html.erb within layouts/dashboard (127.9ms)
Rendered common/_meta_tags.html.erb (1.2ms)
Rendered dashboard/_sidebar.html.erb (23.4ms)
Rendered dashboard/_topbar.html.erb (4.7ms)
Rendered common/_notices.html.erb (2.0ms)
Rendered common/_footer.html.erb (4.9ms)
Completed 200 OK in 980ms (Views: 877.4ms | ActiveRecord: 19.7ms)
Started GET "/assets/application-a2d7fced50ec51954f27ed1fab4792ab0cd3b9b88ff496fc07bd2489660a7c42.css" for 127.0.0.1 at 2018-04-01 14:32:10 +0900
Started GET "/assets/application-e7598b8619f2ba6c7b08ce9a5471f5e4b5223c384a09f8826ae19a864a9bc6b4.js" for 127.0.0.1 at 2018-04-01 14:32:10 +0900
Started GET "/cable" for 127.0.0.1 at 2018-04-01 14:32:11 +0900
Started GET "/assets/bg_logo-db2dd881e08b614d8971e623eb0a14df67c93a5615c04ec2d7c2967893d44b2c.svg" for 127.0.0.1 at 2018-04-01 14:32:11 +0900
Started GET "/cable/" [WebSocket] for 127.0.0.1 at 2018-04-01 14:32:11 +0900
Successfully upgraded to WebSocket (REQUEST_METHOD: GET, HTTP_CONNECTION: Upgrade, HTTP_UPGRADE: websocket)
Started GET "/assets/fontawesome-webfont-ba0c59deb5450f5cb41b3f93609ee2d0d995415877ddfa223e8a8a7533474f07.woff" for 127.0.0.1 at 2018-04-01 14:32:11 +0900
(0.3ms) BEGIN
(1.6ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" DISABLE TRIGGER ALL;ALTER TABLE "subscriptions" DISABLE TRIGGER ALL;ALTER TABLE "listings" DISABLE TRIGGER ALL;ALTER TABLE "advertisements" DISABLE TRIGGER ALL;ALTER TABLE "chatroom_users" DISABLE TRIGGER ALL;ALTER TABLE "chatrooms" DISABLE TRIGGER ALL;ALTER TABLE "messages" DISABLE TRIGGER ALL;ALTER TABLE "pictures" DISABLE TRIGGER ALL;ALTER TABLE "friendly_id_slugs" DISABLE TRIGGER ALL;ALTER TABLE "contact_messages" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
(0.5ms) COMMIT
(56.7ms) TRUNCATE TABLE "public"."subscriptions", "public"."listings", "public"."advertisements", "public"."chatroom_users", "public"."chatrooms", "public"."messages", "public"."pictures", "public"."friendly_id_slugs", "public"."contact_messages", "public"."users" RESTART IDENTITY CASCADE;
(1.7ms) BEGIN
(2.7ms) ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" ENABLE TRIGGER ALL;ALTER TABLE "subscriptions" ENABLE TRIGGER ALL;ALTER TABLE "listings" ENABLE TRIGGER ALL;ALTER TABLE "advertisements" ENABLE TRIGGER ALL;ALTER TABLE "chatroom_users" ENABLE TRIGGER ALL;ALTER TABLE "chatrooms" ENABLE TRIGGER ALL;ALTER TABLE "messages" ENABLE TRIGGER ALL;ALTER TABLE "pictures" ENABLE TRIGGER ALL;ALTER TABLE "friendly_id_slugs" ENABLE TRIGGER ALL;ALTER TABLE "contact_messages" ENABLE TRIGGER ALL;ALTER TABLE "users" ENABLE TRIGGER ALL
(0.5ms) COMMIT

Rspec's expect change count not working

Here I'm testing the changes in current_user.messages.count after the current user sends a valid message. Here's my code:
spec
scenario 'adds to their messages', js: true do
expect { find('#message_content').send_keys(:enter) }.to \
change(current_user.messages, :count).by(1)
end
test.log
# ...
ConversationChannel is transmitting the subscription confirmation
ConversationChannel is streaming from conversation_channel_1
(0.6ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."user_id" = $1 [["user_id", 1]]
ConversationChannel#send_message({"content"=>"foobar\n", "conversation_id"=>"1"})
(0.3ms) BEGIN
(0.9ms) SELECT COUNT(*) FROM "messages" WHERE "messages"."user_id" = $1 [["user_id", 1]]
Conversation Load (1.6ms) SELECT "conversations".* FROM "conversations" WHERE "conversations"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.7ms) SELECT "users"."id" FROM "users" INNER JOIN "user_conversations" ON "users"."id" = "user_conversations"."user_id" WHERE "user_conversations"."conversation_id" = $1 [["conversation_id", 1]]
SQL (1.0ms) INSERT INTO "messages" ("content", "user_id", "conversation_id", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["content", "foobar\n"], ["user_id", 1], ["conversation_id", 1], ["created_at", "2018-01-29 11:27:13.095277"], ["updated_at", "2018-01-29 11:27:13.095277"]]
Finished "/cable/" [WebSocket] for 127.0.0.1 at 2018-01-29 19:27:13 +0800
ConversationChannel stopped streaming from conversation_channel_1
(0.2ms) BEGIN
(58.8ms) COMMIT
(16.7ms) ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "ar_internal_metadata" DISABLE TRIGGER ALL;ALTER TABLE "conversations" DISABLE TRIGGER ALL;ALTER TABLE "messages" DISABLE TRIGGER ALL;ALTER TABLE "user_conversations" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
Rendered messages/_message.html.erb (0.6ms)
[ActionCable] Broadcasting to conversation_channel_1: {:message=>"<p>User 1: foobar\n</p>\n"}
# ...
The spec fails expected #count to have changed by 1, but was changed by 0 even though in the log shows INSERT INTO actually happen.
This doesn't work because you're not waiting long enough for the message addition to actually occur. send_keys returns as soon as the browser has been sent the key event, but knows nothing at all about any request/action triggered by that key press in the browser. This is why direct DB access tests are generally a bad idea in feature/system tests (which should generally just test user visible changes/interactions) and make more sense as request or controller.
That being said you could fix this by just sleeping after sending the key, but a better solution is to use one of the Capybara provided matchers (have waiting/retrying behavior) to synchronize the test.
scenario 'adds to their messages', js: true do
expect do
find('#message_content').send_keys(:enter) }
expect(page).to have_css(...) # check for whatever visible change on the page indicates the action triggered by send_keys has completed
end.to change { current_user.reload.messages.count }.by(1)
end
Note: This test is also very simple for a feature test. It's okay to have multiple expectations in a feature test since it's really meant to test a whole user interaction with a specific feature of your app. You might want to look at combining this test with other tests of the same part of your app.
Try to write :
change{current_user.messages, :count}.by(1)
with {}

RSpec use_transactional_fixtures=false not working

Setting use_transactional_fixtures=false in my spec_helper.rb file, though when I look at the output of my test log, it appears a transaction is still being used? Why is that the case and how do I eliminate the transaction?
rails_helper.rb...
# This file is copied to spec/ when you run 'rails generate rspec:install'
ENV["RAILS_ENV"] ||= 'test'
require 'spec_helper'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
# Add additional requires below this line. Rails is not loaded until this point!
ActiveRecord::Migration.maintain_test_schema!
RSpec.configure do |config|
# Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
config.fixture_path = "#{::Rails.root}/spec/fixtures"
config.use_transactional_fixtures = false
config.infer_spec_type_from_file_location!
config.include FactoryGirl::Syntax::Methods
end
spec_helper.rb...
require 'devise'
require "paperclip/matchers"
require 'simplecov'
require 'database_cleaner'
require 'capybara/rspec'
SimpleCov.start 'rails'
module SphinxHelpers
def index
ThinkingSphinx::Test.index
# Wait for Sphinx to finish loading in the new index files.
sleep 0.25 until index_finished?
end
def index_finished?
Dir[Rails.root.join(ThinkingSphinx::Test.config.indices_location, '*.{new,tmp}*')].empty?
end
end
RSpec.configure do |config|
config.include SphinxHelpers
config.before(:suite) do
# Ensure sphinx directories exist for the test environment
ThinkingSphinx::Test.init
# Configure and start Sphinx, and automatically
# stop Sphinx at the end of the test suite.
ThinkingSphinx::Test.start_with_autostop
end
config.before(:each) do |example|
index
end
config.include Paperclip::Shoulda::Matchers
config.include Devise::TestHelpers, :type => :controller
config.raise_errors_for_deprecations!
config.before(:suite) do
DatabaseCleaner.strategy = :transaction
DatabaseCleaner.clean_with(:truncation)
end
config.around(:each) do |example|
DatabaseCleaner.cleaning do
example.run
end
end
config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
config.mock_with :rspec do |mocks|
mocks.verify_partial_doubles = true
end
end
RSpec::Matchers.define :accept_nested_attributes_for do |association|
match do |model|
#model = model
#nested_att_present = model.respond_to?("#{association}_attributes=".to_sym)
if #nested_att_present && #reject
model.send("#{association}_attributes=".to_sym,[#reject])
#reject_success = model.send("#{association}").empty?
end
if #nested_att_present && #accept
model.send("#{association}_attributes=".to_sym,[#accept])
#accept_success = ! (model.send("#{association}").empty?)
end
#nested_att_present && ( #reject.nil? || #reject_success ) && ( #accept.nil? || #accept_success )
end
failure_message_for_should do
messages = []
messages << "expected #{#model.class} to accept nested attributes for #{association}" unless #nested_att_present
messages << "expected #{#model.class} to reject values #{#reject.inspect} for association #{association}" unless #reject_success
messages << "expected #{#model.class} to accept values #{#accept.inspect} for association #{association}" unless #accept_success
messages.join(", ")
end
description do
desc = "accept nested attributes for #{expected}"
if #reject
desc << ", but reject if attributes are #{#reject.inspect}"
end
end
chain :but_reject do |reject|
#reject = reject
end
chain :and_accept do |accept|
#accept = accept
end
end
a spec that displays the problem....
require 'rails_helper'
RSpec.describe "The homepage", :type => :feature do
it "displays articles" do
article = create(:article)
visit "/"
assert page.has_css?(".article")
end
end
the test log output when running the spec (note the transaction)...
(0.2ms) ALTER TABLE "annotations" DISABLE TRIGGER ALL;ALTER TABLE "categories" DISABLE TRIGGER ALL;ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "articles" DISABLE TRIGGER ALL;ALTER TABLE "comments" DISABLE TRIGGER ALL;ALTER TABLE "contents" DISABLE TRIGGER ALL;ALTER TABLE "galleries" DISABLE TRIGGER ALL;ALTER TABLE "gallery_images" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
(13.5ms) TRUNCATE TABLE "annotations", "categories", "articles", "comments", "contents", "galleries", "gallery_images", "users" RESTART IDENTITY CASCADE;
(0.7ms) ALTER TABLE "annotations" ENABLE TRIGGER ALL;ALTER TABLE "categories" ENABLE TRIGGER ALL;ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "articles" ENABLE TRIGGER ALL;ALTER TABLE "comments" ENABLE TRIGGER ALL;ALTER TABLE "contents" ENABLE TRIGGER ALL;ALTER TABLE "galleries" ENABLE TRIGGER ALL;ALTER TABLE "gallery_images" ENABLE TRIGGER ALL;ALTER TABLE "users" ENABLE TRIGGER ALL
ActiveRecord::SchemaMigration Load (0.3ms) SELECT "schema_migrations".* FROM "schema_migrations"
(0.6ms) ALTER TABLE "annotations" DISABLE TRIGGER ALL;ALTER TABLE "categories" DISABLE TRIGGER ALL;ALTER TABLE "schema_migrations" DISABLE TRIGGER ALL;ALTER TABLE "articles" DISABLE TRIGGER ALL;ALTER TABLE "comments" DISABLE TRIGGER ALL;ALTER TABLE "contents" DISABLE TRIGGER ALL;ALTER TABLE "galleries" DISABLE TRIGGER ALL;ALTER TABLE "gallery_images" DISABLE TRIGGER ALL;ALTER TABLE "users" DISABLE TRIGGER ALL
(1.0ms) select table_name from information_schema.views where table_schema = 'site_test'
(23.8ms) TRUNCATE TABLE "annotations", "categories", "articles", "comments", "contents", "galleries", "gallery_images", "users" RESTART IDENTITY CASCADE;
(0.7ms) ALTER TABLE "annotations" ENABLE TRIGGER ALL;ALTER TABLE "categories" ENABLE TRIGGER ALL;ALTER TABLE "schema_migrations" ENABLE TRIGGER ALL;ALTER TABLE "articles" ENABLE TRIGGER ALL;ALTER TABLE "comments" ENABLE TRIGGER ALL;ALTER TABLE "contents" ENABLE TRIGGER ALL;ALTER TABLE "galleries" ENABLE TRIGGER ALL;ALTER TABLE "gallery_images" ENABLE TRIGGER ALL;ALTER TABLE "users" ENABLE TRIGGER ALL
(0.1ms) BEGIN
(0.1ms) COMMIT
(0.1ms) BEGIN
(0.1ms) SAVEPOINT active_record_1
User Exists (0.5ms) SELECT 1 AS one FROM "users" WHERE "users"."username" = 'testuser' LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'testuser#miscia.net' LIMIT 1
User Exists (0.2ms) SELECT 1 AS one FROM "users" WHERE "users"."email" = 'testuser#miscia.net' LIMIT 1
SQL (0.7ms) INSERT INTO "users" ("created_at", "email", "encrypted_password", "updated_at", "username") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", "2015-03-08 20:48:58.017027"], ["email", "testuser#miscia.net"], ["encrypted_password", "$2a$04$vHQdcMEuRC2mdrNebemS5e9zMOUaloIhcIBEHlu6z7ms8Rh2NJ0F."], ["updated_at", "2015-03-08 20:48:58.017027"], ["username", "testuser"]]
(0.2ms) RELEASE SAVEPOINT active_record_1
Command :: file -b --mime '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hsarwm.png'
Command :: identify -format '%wx%h,%[exif:orientation]' '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hlhno1.png[0]' 2>/dev/null
Command :: identify -format %m '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hlhno1.png[0]'
Command :: convert '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hlhno1.png[0]' -auto-orient -resize "100%" '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hlhno120150308-5197-1j86yzw'
Command :: file -b --mime '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-hlhno120150308-5197-1j86yzw'
(0.1ms) SAVEPOINT active_record_1
Command :: file -b --mime '/var/folders/px/vlftvj1j43j7369cy2_sz_j40000gn/T/aacb8db341826e828273a80db5d60d5220150308-5197-h4xfii.png'
Article Exists (0.5ms) SELECT 1 AS one FROM "articles" WHERE "articles"."title" = 'Article Title' LIMIT 1
SQL (6.4ms) INSERT INTO "articles" ("body", "created_at", "description", "header_one", "header_two", "photo_content_type", "photo_file_name", "photo_file_size", "photo_updated_at", "posted_at", "title", "updated_at", "url", "user_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING "id" [["body", "This is the article body."], ["created_at", "2015-03-08 20:48:58.218394"], ["description", "This is a test article!"], ["header_one", "Article Header One"], ["header_two", "Article Header Two"], ["photo_content_type", "image/png"], ["photo_file_name", "test_image.png"], ["photo_file_size", 96884], ["photo_updated_at", "2015-03-08 20:48:58.036313"], ["posted_at", "2015-03-08 20:48:51.398651"], ["title", "Article Title"], ["updated_at", "2015-03-08 20:48:58.218394"], ["url", "http://www.testurl.com"], ["user_id", 1]]
(0.2ms) RELEASE SAVEPOINT active_record_1
Started GET "/" for 127.0.0.1 at 2015-03-08 16:48:58 -0400
Processing by SiteController#index as HTML
Sphinx Query (9.3ms) SELECT * FROM `article_core` WHERE `sphinx_deleted` = 0 ORDER BY `created_at` DESC LIMIT 0, 5
Sphinx Found 0 results
Rendered articles/_pagination.html.erb (0.7ms)
Rendered articles/_list.html.erb (13.0ms)
Category Load (0.3ms) SELECT "categories".* FROM "categories"
Rendered site/homepage/_filters.html.erb (1.3ms)
Rendered site/homepage/_search.html.erb (57.1ms)
Rendered site/homepage/_twitter.html.erb (0.5ms)
Rendered site/homepage/_sidebar.html.erb (61.2ms)
Rendered site/index.html.erb within layouts/application (76.3ms)
Rendered site/_head.html.erb (19.8ms)
Rendered site/homepage/_social_media.html.erb (0.5ms)
Rendered site/homepage/_social_media.html.erb (0.1ms)
Rendered site/homepage/_social_media.html.erb (0.1ms)
Rendered site/homepage/_social_media.html.erb (0.1ms)
Rendered site/homepage/_menu.html.erb (8.0ms)
Content Load (0.4ms) SELECT "contents".* FROM "contents" WHERE "contents"."name_slug" = 'footer_left' ORDER BY "contents"."id" ASC LIMIT 1
Content Load (0.3ms) SELECT "contents".* FROM "contents" WHERE "contents"."name_slug" = 'footer_middle' ORDER BY "contents"."id" ASC LIMIT 1
Rendered site/_footer.html.erb (8.6ms)
Rendered site/_ga.html.erb (0.4ms)
Completed 200 OK in 128ms (Views: 116.4ms | ActiveRecord: 2.2ms)
(0.2ms) ROLLBACK
When you set config.use_transactional_fixtures = false you are telling RSpec to not bother cleaning the database each time it starts a new example. Sometimes I set this to false because I instead use database_cleaner (GitHub) to handle it.
So, based on your logs, I believe RSpec is doing exactly what it is supposed to be doing.
You can read about this setting in the RSpec manual

Rails activeRecord patch and commit

I'm working on a tagged blog system using rails. Now it works using join table, but I have some doubt on performance.
Now every time I create a blog and its associating tags, it commits to database every single statement. I think better practice should be patch all these statements and commit to database only once. Is it possible in rails?
Create log dump:
Started POST "/articles" for 127.0.0.1 at 2014-08-29 15:30:48 -0400
Processing by ArticlesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"K4rWQEh0810X2jMe/Z9pC/PN2WeOcO0F0TkjKUZTPec=", "title"=>"test27", "text"=>"ddddd", "tag"=>"a,k,g", "commit"=>"Submit"}
Unpermitted parameters: utf8, authenticity_token, tag, commit
(0.9ms) BEGIN
SQL (0.3ms) INSERT INTO `articles` (`created_at`, `text`, `title`, `updated_at`) VALUES ('2014-08-29 19:30:48', 'ddddd', 'test27', '2014-08-29 19:30:48')
(0.2ms) COMMIT
Tag Load (0.3ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'a' LIMIT 1
(0.2ms) BEGIN
SQL (0.3ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 1)
(0.2ms) COMMIT
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'k' LIMIT 1
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO `tags` (`name`) VALUES ('k')
(0.1ms) COMMIT
(0.1ms) BEGIN
SQL (0.3ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 17)
(8.3ms) COMMIT
Tag Load (0.3ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'g' LIMIT 1
(0.1ms) BEGIN
SQL (0.2ms) INSERT INTO `tags` (`name`) VALUES ('g')
(0.1ms) COMMIT
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 18)
(0.2ms) COMMIT
Model design:
class Article < ActiveRecord::Base
has_and_belongs_to_many :tag
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :article, :uniq => true
end
Blog create code:
def create
#article = Article.new(article_params)
tag_arr = params[:tag].split(',')
if #article.save
tag_arr.each do |name|
tag = Tag.find_or_create_by(name: name) # create a new tag only if tag.name not exist
#article.tag << tag
end
redirect_to #article
else
render 'new'
end
end
To save the article and tags in a single transaction, wrap the operations in a transaction block.
def create
#article = Article.new(article_params)
tag_arr = params[:tag].split(',')
Article.transaction do
if #article.save
tag_arr.each do |name|
article.tags.find_or_create_by(name: name)
end
end
end
if #article.new_record?
render 'new'
else
redirect_to #article
end
end
This will result in SQL queries something like:
(0.9ms) BEGIN
SQL (0.3ms) INSERT INTO `articles` (`created_at`, `text`, `title`, `updated_at`) VALUES ('2014-08-29 19:30:48', 'ddddd', 'test27', '2014-08-29 19:30:48')
Tag Load (0.3ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'a' LIMIT 1
SQL (0.3ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 1)
Tag Load (0.2ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'k' LIMIT 1
SQL (0.2ms) INSERT INTO `tags` (`name`) VALUES ('k')
SQL (0.3ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 17)
Tag Load (0.3ms) SELECT `tags`.* FROM `tags` WHERE `tags`.`name` = 'g' LIMIT 1
SQL (0.2ms) INSERT INTO `tags` (`name`) VALUES ('g')
SQL (0.4ms) INSERT INTO `articles_tags` (`article_id`, `tag_id`) VALUES (28, 18)
(0.2ms) COMMIT

Resources