How to identify Rails 6 bulk insert error - ruby-on-rails

I have the following relations set up:
user has_many quizzes
quiz belongs_to user
quiz has_many questions
question belongs_to quiz
App is set up to use PostgreSQL. I'm trying to bulk insert a bunch of records using the insert_all! method
begin
quiz = user.quizzes.create!(title: title, slug: slug)
quiz_questions = params[:quiz][:questions].map! do |q|
# creating an attribute hash here (code removed for conciseness of question)
end
result = quiz.questions.insert_all!(quiz_questions)
This threw an error which was caught by my "catch all" block
rescue ActiveRecord::ActiveRecordError
render json: { message: ['Something went wrong'] }, status: 500
The running server console printed this message:
TRANSACTION (0.9ms) BEGIN
↳ app/controllers/quizzes_controller.rb:14:in `create'
Quiz Create (2.8ms) INSERT INTO "quizzes" ("title", "user_id", "slug", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "a quiz"], ["user_id", 1], ["slug", "a-quizk2DqYk"], ["created_at", "2021-12-01 05:00:05.800134"], ["updated_at", "2021-12-01 05:00:05.800134"]]
↳ app/controllers/quizzes_controller.rb:14:in `create'
TRANSACTION (1.6ms) COMMIT
↳ app/controllers/quizzes_controller.rb:14:in `create'
Question Bulk Insert (0.6ms) INSERT INTO "questions" ("question","a","b","c","d","score","answer","quiz_id") VALUES ('what is name', 'str', 'char', 'num', 'bool', 5, 'A', 1), ('die', 'yes', 'no', 'ok', 'what', 5, 'B', 1) RETURNING "id"
↳ (eval):6:in `block in insert_all!'
Completed 500 Internal Server Error in 153ms (Views: 0.2ms | ActiveRecord: 38.1ms | Allocations: 49609)
So I think I am not calling insert_all! correctly because the server just does an insert without the BEGIN and COMMIT bookends. Also, I would like to know which error is being thrown and caught by the catch all block. What would be the correct way to do insert_all! ?

you could wrap your bulk insert into a transaction
def bulk_insert
ActiveRecord::Base.transaction do
quiz = user.quizzes.create!(title: title, slug: slug)
quiz_questions = params[:quiz][:questions].map! do |q|
# creating an attribute hash here
# ...
# note that you could validate attribute manually
raise ActiveRecord::Rollback if q.description.blank?
end
result = quiz.questions.insert_all!(quiz_questions)
end
rescue ActiveRecord::Rollback => e
puts e
end

Related

Model create overrides custom params :created_at

I am uploading legacy articles to my Rails app. I am sending created_at as a parameter in my request as recommended in this answer. However, this attribute seemingly is not passed "through". I can puts(params[:created_at]) and see my custom created_at, yet in the logs the article is INSERTed with a created_at of the current timestamp.
Here is my articles controller:
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :update, :destroy]
...
# POST /articles
def create
#section = Section.friendly.find(params[:section_id])
# Can't let people publish by default
#article = #section.articles.build(
article_params.merge(is_published: false)
)
if #article.save
render json: #article, status: :created, location: #article
else
render json: #article.errors, status: :unprocessable_entity
end
end
end
My request is:
http POST :3000/articles title='example' section_id=1 content="<p>the section exists.</p>" slug="example" created_at="2017-06-109T17:57:55.149-05:00"
The logs:
Started POST "/articles" for 127.0.0.1 at 2017-11-24 12:05:06 -0500
Processing by ArticlesController#create as HTML
Parameters: {"title"=>"example", "section_id"=>"1", "content"=>"<p>the section exists.</p>", "slug"=>"example", "created_at"=>"2017-06-109T17:57:55.149-05:00", "article"=>{"title"=>"example", "slug"=>"example", "content"=>"<p>the section exists.</p>", "created_at"=>"2017-06-109T17:57:55.149-05:00", "section_id"=>"1"}}
Section Load (0.3ms) SELECT "sections".* FROM "sections" WHERE "sections"."slug" = $1 ORDER BY "sections"."id" ASC LIMIT $2 [["slug", "1"], ["LIMIT", 1]]
Section Load (0.4ms) SELECT "sections".* FROM "sections" WHERE "sections"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
(0.2ms) BEGIN
SQL (0.6ms) INSERT INTO "articles" ("title", "slug", "content", "is_published", "created_at", "updated_at", "section_id") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["title", "example"], ["slug", "example"], ["content", "<p>the section exists.</p>"], ["is_published", "f"], ["created_at", "2017-11-24 12:05:06.175751"], ["updated_at", "2017-11-24 12:05:06.175751"], ["section_id", 1]]
(0.8ms) COMMIT
Completed 201 Created in 38ms (Views: 1.1ms | ActiveRecord: 8.0ms)
In my schema.rb:
create_table "articles", force: :cascade do |t|
...
t.datetime "created_at", null: false
My model has no extra methods/callbacks that would ruin the request. It only contains relationships. I don't know if this is relevant, but I saw it in a GitHub issue somewhere: articles and users are in a many2many with an authorships model, and I use devise to authenticate users.
In conclusion, the app is receiving the created_at param just fine, but it is overridden with the default timestamp. If I set record_timestamps to false, the created_at just becomes nil.
Why is my created_at just seemingly ignored?
Rails 5.1, Ruby 2.4.2, Postgres 10.1
Answer given by max: there was a syntatical error in my timestamp. It was not valid, and was ignored by Rails.

Rails ajax call 500 internal server error

I'm having trouble working with respond_to method: when the model got saved, it returns in json format correctly. But, when the model didn't get saved, because a error like ActiveRecord::RecordNotUnique, rails returns another respond that it's not my: "Completed 500 Internal Server Error" and the respond returned goes to ajax error fuction.
My question here is, how can I send the correct respond, the one inside the else statement, so I can show to users the correct errors messages.
I'm in development mode.
I change the code a little just for simplicity.
Thanks.
Controller:
def create
#distribuicao = DistribuicaoPorCargo.new(distribuicao_por_cargo_params)
respond_to do |format|
if #distribuicao.save
format.json { render json: #distribuicao.distribuicao, status: :created }
else
format.json { render json: #distribuicao.errors.full_messages, status: :unprocessable_entity }
end
end
end
.js:
$.ajax
url: '/distribuicao_por_cargos'
type: 'POST'
dataType: 'JSON'
data: {
data: JSON.stringify(data_send_to_server)
}
success: (data, textStatus, jqXHR) ->
console.log("AJAX Sucesss: #{textStatus}")
error: (jqXHR, textStatus, errorThrown) ->
console.log("AJAX Failed: #{textStatus}")
log:
Started POST "/distribuicao_por_cargos" for 187.110.216.111 at 017-06-02 17:32:22 -0300
Cannot render console from 187.110.216.111! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by DistribuicaoPorCargosController#create as JSON
Parameters: {"concurso_id"=>"2", "local_da_prova_id"=>"6", "tabela"=>"{\"cargo_id\":\"4\",\"quantidade_de_candidatos\":\"10\"},{\"cargo_id\":\"9\",\"quantidade_de_candidatos\":\"10\"},{\"cargo_id\":\"12}]"}
Concurso Load (0.4ms) SELECT "concursos".* FROM "concursos" WHERE "concursos"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
LocalDaProva Load (0.2ms) SELECT "local_da_provas".* FROM "local_da_provas" WHERE "local_da_provas"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]]
(0.1ms)
BEGIN
LocalDaProva Load (0.3ms) SELECT "local_da_provas".* FROM "local_da_provas" WHERE "local_da_provas"."id" = $1 LIMIT $2 [["id", 6], ["LIMIT", 1]] SQL (1.5ms)
INSERT INTO "distribuicao_por_cargos" ("created_at", "updated_at", "local_da_prova_id", "distribuicao") VALUES ($1, $2, $3, $4) RETURNING "id" [["created_at", 2017-06-02 20:32:23 UTC], ["updated_at", 2017-06-02 20:32:23 UTC], ["local_da_prova_id", 6], ["distribuicao", "[{\"cargo_id\":\"4\",\"quantidade_de_candidatos\":\"10\"},{\"cargo_id}]"]]
(0.2ms)
ROLLBACK
Completed 500 Internal Server Error in 108ms (ActiveRecord: 6.3ms)
ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_distribuicao_por_cargos_on_local_da_prova_id"
DETAIL: Key (local_da_prova_id)=(6) already exists.
: INSERT INTO "distribuicao_por_cargos" ("created_at", "updated_at", "local_da_prova_id", "distribuicao") VALUES ($1, $2, $3, $4) RETURNING "id"):
This happens because you are not validating in your model, so when it tries to create the record it fails, with an exception.
To avoid that, you could add a validation in your model, which will check if the column is unique; if validation succeeds then the record will be created, otherwise it will return false and create an error message (instead of raising an exception).
You can do this adding uniqueness: true in your model validations, like this:
class DistribuicaoPorCargo < ApplicationRecord
# scopes, callbacks, ...
validates :local_da_prova_id, uniqueness: true
# more validations
end
This way, your controller will respond with:
render json: #distribuicao.errors.full_messages, status: :unprocessable_entity
and won't raise an exception (which prevent Completed 500 Internal Server Error from showing up).

ruby on rails - Unique multiple key index issue in production (Heroku PostgreSQL)

I am facing an issue I have tried to solve by myself but I don't get what's wrong. I am French so my models, controllers... have french names.
I am developing an app for an association that makes what we call in French "maraudes" every night : driving around to meet people.
I have this Maraude model :
class Maraude < ActiveRecord::Base
default_scope -> { order(date: :desc) }
validates :date, presence: true,
uniqueness: { scope: :type_maraude }
validates :type_maraude, presence: true
end
My maraudes have :date and :type_maraude attributes, and I want them to define every maraude, so that every maraude has a unique [:date, :type_maraude] combination.
In order to do this, I also created this migration :
class AddIndexToMaraudes < ActiveRecord::Migration
def change
add_index :maraudes, [:date, :type_maraude], unique: true
end
end
It is perfectly working in development. In production (Heroku), I have created a maraude dated 2016-02-02 with a certain type (Maraude salariés 1) and when trying to create an other maraude at the same date but with a different type I get the following error and I don't understand where it may come from :
2016-02-05T13:56:51.810031+00:00 app[web.1]: Started POST "/maraudes" for 80.13.244.250 at 2016-02-05 13:56:51 +0000
2016-02-05T13:56:51.823195+00:00 app[web.1]: Maraude Exists (0.9ms) SELECT 1 AS one FROM "maraudes" WHERE ("maraudes"."date" = '2016-02-02' AND "maraudes"."type_maraude" = 'Maraude bénévoles') LIMIT 1
2016-02-05T13:56:51.812447+00:00 app[web.1]: Processing by MaraudesController#create as HTML
2016-02-05T13:56:51.829572+00:00 app[web.1]: Completed 500 Internal Server Error in 17ms (ActiveRecord: 5.9ms)
2016-02-05T13:56:51.819326+00:00 app[web.1]: Maraude Load (0.9ms) SELECT "maraudes".* FROM "maraudes" WHERE "maraudes"."date" = $1 AND "maraudes"."type_maraude" = $2 ORDER BY "maraudes"."date" DESC LIMIT 1 [["date", "2016-02-02"], ["type_maraude", "Maraude bénévoles"]]
2016-02-05T13:56:51.812533+00:00 app[web.1]: Parameters: {"utf8"=>"✓", "authenticity_token"=>"useo/o+NPJ6cemrRfdUN2LRIsDCAHiseYTlV2EKXigH/6C47ZzEhtM0mCra7EuY/QY/nZuXdpfnTDxR4VugUdw==", "maraude"=>{"date"=>"2016-02-02", "type_maraude"=>"Maraude bénévoles"}, "commit"=>"Créer la maraude"}
2016-02-05T13:56:51.828110+00:00 app[web.1]: DETAIL: Key (date)=(2016-02-02) already exists.
2016-02-05T13:56:51.814745+00:00 app[web.1]: User Load (0.9ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
2016-02-05T13:56:51.820512+00:00 app[web.1]: (0.7ms) BEGIN
2016-02-05T13:56:51.828073+00:00 app[web.1]: SQL (1.6ms) INSERT INTO "maraudes" ("date", "type_maraude", "villes", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["date", "2016-02-02"], ["type_maraude", "Maraude bénévoles"], ["villes", ""], ["created_at", "2016-02-05 13:56:51.823377"], ["updated_at", "2016-02-05 13:56:51.823377"]]
2016-02-05T13:56:51.828108+00:00 app[web.1]: PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_maraudes_on_date"
2016-02-05T13:56:51.828111+00:00 app[web.1]: : INSERT INTO "maraudes" ("date", "type_maraude", "villes", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"
2016-02-05T13:56:51.829215+00:00 app[web.1]: (0.9ms) ROLLBACK
2016-02-05T13:56:51.832064+00:00 app[web.1]:
2016-02-05T13:56:51.832067+00:00 app[web.1]: ActiveRecord::RecordNotUnique (PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "index_maraudes_on_date"
2016-02-05T13:56:51.832068+00:00 app[web.1]: DETAIL: Key (date)=(2016-02-02) already exists.
2016-02-05T13:56:51.832069+00:00 app[web.1]: : INSERT INTO "maraudes" ("date", "type_maraude", "villes", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id"):
2016-02-05T13:56:51.832070+00:00 app[web.1]: app/controllers/maraudes_controller.rb:34:in `create'
The PG error seems to indicate that it behaves like I had created a unique index on :date which is not the case...
For info here is my 'create' action in my Maraudes controller.
EDIT : I translated the flash messages.
def create
#maraude = Maraude.new(maraude_params)
#maraude.villes = ""
if Maraude.find_by(date: params[:maraude][:date], type_maraude: params[:maraude][:type_maraude])
flash[:danger] = "This maraude already exists"
redirect_to new_maraude_path
elsif #maraude.save
flash[:success] = "Maraude created"
redirect_to id_m_villes_path(id: #maraude.id)
else
flash[:danger] = "Give date and maraude type"
redirect_to new_maraude_path
end
end
Thx for your help !

nested attributes works for create but fails when update a record

I have a problem with nested attributes. Creating works but when I update, the error message shows me that the values in the relation are not set. I can't find the reason.
The main model
class Product < ActiveRecord::Base
has_many :product_options
accepts_nested_attributes_for :product_options,
:allow_destroy => true,
:reject_if => proc { | r | r["name"].blank? or r["value"].blank? }
end
The nested model
class ProductOption < ActiveRecord::Base
belongs_to :product
validates :name, :presence => true
validates :value, :presence => true
end
The controller is a bit shorted.
Items is a model where Product is related to as has_one
class Admin::ProductsController < Admin::ApplicationController
before_action :set_product, only: [ :new, :show, :edit, :update, :destroy ]
def create
#product = Product.new( product_params )
respond_to do |format|
if #product.save
#product.product_options.build
format.js { render :js => "alert( 'Daten gespeichert!' );" }
else
format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
end
end
end
def update
respond_to do |format|
if #product.update( product_params )
format.js { render :js => "alert( 'Daten gespeichert!' );" }
else
require "pp"
pp #product.errors
format.js { render :js => 'alert( "Fehler beim Speichern!" );' }
end
end
end
private
# UPDATE: creating the product_options at this time
# produces the described error :)
def set_product
#item = Item.find_by_id( params[ :item_id ] ) if params[ :item_id ]
#product = #item.product ? #item.product : #item.build_product
# WRONG Place for generating new options
# 2.times { #product.product_options.build }
end
def product_params
params.require( :product ).permit( :item_id, :name, :title, :active, :product_options_attributes => [ :id, :name, :value, :_destroy ] )
end
end
The console output for the create is working and looks like
Started POST "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:12:14 +0100
Processing by Admin::ProductsController#create as JS
Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa"}}}, "commit"=>"Create Product", "item_id"=>"653"}
User Load (1.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
(0.6ms) BEGIN
SQL (17.5ms) INSERT INTO "products" ("created_at", "item_id", "name", "title", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["item_id", 653], ["name", "1"], ["title", "1"], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00]]
SQL (1.3ms) INSERT INTO "product_options" ("created_at", "name", "product_id", "updated_at", "value") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["created_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["name", "aaa"], ["product_id", 28], ["updated_at", Fri, 14 Feb 2014 14:12:14 UTC +00:00], ["value", "aaaa"]]
Item Load (1.0ms) SELECT "items".* FROM "items" WHERE "items"."id" = $1 ORDER BY "items"."id" ASC LIMIT 1 [["id", 653]]
ProductOption Load (1.3ms) SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1 [["product_id", 28]]
Rendered admin/products/_show.html.erb (7.6ms)
Rendered admin/products/create.js.erb (9.2ms)
Completed 200 OK in 448ms (Views: 40.0ms | ActiveRecord: 27.1ms)
The the update. I doesn't work and gives an error that the nested fields are empty. It's the pp inside the update method
Started PATCH "/admin/items/653/product" for 127.0.0.1 at 2014-02-14 15:15:03 +0100
Processing by Admin::ProductsController#update as JS
Parameters: {"utf8"=>"✓", "product"=>{"item_id"=>"653", "name"=>"1", "title"=>"1", "active"=>"1", "product_options_attributes"=>{"0"=>{"name"=>"aaa", "value"=>"aaaa", "id"=>"9"}, "1"=>{"name"=>"bbb", "value"=>"bbbb"}}}, "commit"=>"Update Product", "item_id"=>"653"}
User Load (1.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = 6 ORDER BY "users"."id" ASC LIMIT 1
Item Load (0.6ms) SELECT "items".* FROM "items" WHERE "items"."id" = 653 LIMIT 1
Product Load (0.9ms) SELECT "products".* FROM "products" WHERE "products"."item_id" = $1 ORDER BY "products"."id" ASC LIMIT 1 [["item_id", 653]]
(0.6ms) BEGIN
ProductOption Load (1.3ms) SELECT "product_options".* FROM "product_options" WHERE "product_options"."product_id" = $1 AND "product_options"."id" IN (9) [["product_id", 28]]
(0.5ms) ROLLBACK
#<ActiveModel::Errors:0x007f8bdeb9f818
#base=
#<Product id: 28, item_id: 653, content: nil, active: 1, created_at: "2014-02-14 14:12:14", updated_at: "2014-02-14 14:12:14", name: "1", title: "1", ordernumber: "">,
#messages=
{:"product_options.name"=>["can't be blank"],
:"product_options.value"=>["can't be blank"]}>
Completed 200 OK in 18ms (Views: 0.1ms | ActiveRecord: 5.1ms)
I think I know what is the problem with your code. The accepts_nested_attributes_for does not require you to build any of the associated models. If the appropriate params are passed in then the model automatically builds or updates the associations.
In your case in the update method what you do is the following:
You find the relevant product. So far so good (although you could actually use a specific product id in your form)
Then you build two product options (in #set_product). This is the problem.
And in the end you update the model based on the parameters.
Now the problem with the second step is that you basically build two empty associated instances. Those are not affected by the accepts_nested_attributes. As a result you are trying to save 2+2 product options (the ones you build and the ones created by the params). Obviously you get the validation error due to the fact the two of the models have no attributes set.
You can make sure my hypothesis is correct by removing the validators from ProductOption. On update you should get 4 associated product options persisted.

How do I pass commentable into mailer?

I have this in the Model:
after_create do |comment|
CommentMailer.comment_email(self).deliver
end
This in CommentMailer:
class CommentMailer < ActionMailer::Base
helper ActionView::Helpers::UrlHelper
include CommentHelper
helper :comment
def comment_email(user, comment, commentable)
mail(to: user.email,
subject: "You have left a comment",
from: "comments#lumeo.com",
bcc: "brian#lumeo.com")
end
end
And this in CommentHelper:
module CommentHelper
def find_commentable
#comment = Comment.find(params[:comment])
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
end
I'm getting this error:
Started POST "/requests/6/comments" for 127.0.0.1 at 2012-11-30 17:28:55 -0800
Processing by CommentsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"R62NH5/EE34FPapEqy7mfpa0wKz18GtSdhH8MGYq2Ec=", "comment"=>{"content"=>"post", "show"=>"true"}, "commit"=>"Create Comment", "request_id"=>"6"}
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = 2 ORDER BY users.created_at DESC LIMIT 1
Request Load (0.3ms) SELECT "requests".* FROM "requests" WHERE "requests"."id" = $1 LIMIT 1 [["id", "6"]]
CACHE (0.0ms) SELECT "requests".* FROM "requests" WHERE "requests"."id" = $1 LIMIT 1 [["id", "6"]]
(0.1ms) BEGIN
SQL (0.4ms) INSERT INTO "comments" ("commentable_id", "commentable_type", "content", "created_at", "show", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id" [["commentable_id", 6], ["commentable_type", "Request"], ["content", "post"], ["created_at", Sat, 01 Dec 2012 01:28:55 UTC +00:00], ["show", true], ["updated_at", Sat, 01 Dec 2012 01:28:55 UTC +00:00], ["user_id", 2]]
(0.2ms) ROLLBACK
Completed 500 Internal Server Error in 136ms
ArgumentError (wrong number of arguments (1 for 3)):
app/mailers/comment_mailer.rb:5:in `comment_email'
app/models/comment.rb:27:in `block in <class:Comment>'
app/controllers/comments_controller.rb:22:in `create'
Looks like simple typos.
Line 7, as noted in the exception:
commentable = #comment.commentable
So, the issues:
You're calling #comment.commentabe, but #comment is nil
Hence the error: undefined method 'commentable' for nil:NilClass
#comment is nil in your mailer method because you're passing it in as comment NOT #comment, yet you're trying to reference it as #comment.
Also, why are you passing in commentable as a parameter, but on line 7 you're setting commentable again - this is redundant? Just use the already available commentable variable that you're passing in as a param. In fact, you seem to be doing this with several variables, yet I can't tell (because you don't show the mailer template) whether or not you're actually using them.
It could be that you could use something simpler like:
So, this should (probably) work:
def comment_email(user, comment, commentable)
mail(to: user.email,
subject: "You have left a comment",
from: "comments#lumeo.com",
bcc: "brian#lumeo.com")
end
If you post your mail template (so I can see what the body of the email looks like) I can help you get the variables into the template.

Resources