I have a model with 2 associations and no validations, which can be created but if I want to destroy it, I get error " ActiveRecord::RecordNotDestroyed"
So I tried directly in rails create new record for this Model and then run destroy, but it rollbacks without any error message. If I do destroy! I get the error. I enabled mysql query logging but it looks it doesnt even get to delete part so I assume rails is somehow preventing from destroying this model.
As a test I created dummy model without any association, with just one string value. Same issue in rails console. I have to note that .delete method works, but .destroy not
Then I started to analyze in my rails app (I took it over from somebody so I am still not 100% familiar) and in rails console tried to destroy some records for different models and realized some I am able to destroy, some not.
Is there any way how to find out why I am not able to destroy those records? It is really strange for my Testmodel without any validation or association. Is there anywhere to look for more info? I tried this:
testrecord.destroy; testrecord.errors but in #messages I have empty {}
Here is my code for model where I found an issue (EinvoiceContact) and also my Testmodel
class EinvoiceContact < ActiveRecord::Base
belongs_to :customer
has_many :building
end
class Customer < ActiveRecord::Base
has_many :einvoice_contacts, dependent: :destroy
accepts_nested_attributes_for :einvoice_contacts, allow_destroy: true, reject_if: :empty_einvoice_contact
end
class Testmodel < ActiveRecord::Base
end
and here is output from rails and mysql log
> irb(main):045:0> Testmodel.find(1).destroy\r Testmodel Load (0.4ms)
> SELECT `testmodels`.* FROM `testmodels` WHERE `testmodels`.`id` = 1
> LIMIT 1 (0.1ms) BEGIN (0.1ms) ROLLBACK
> => false
>
>
> 2019-12-18T21:05:42.455378Z 33 Query SELECT `testmodels`.* FROM
> `testmodels` WHERE `testmodels`.`id` = 1 LIMIT 1
> 2019-12-18T21:05:42.456559Z 33 Query BEGIN
> 2019-12-18T21:05:42.458261Z 33 Query ROLLBACK
Any help where to look for more details, or where this can be set is appreciated. This app uses CanCan but I have set
can :manage, :all
Rails is 4.2.8 with ruby 2.1.4 (I know it is old app, I just took it over and maintain), mysql
thanks
It seems that Testmodel has some associations.
Try this in the console and check the output.
Testmodel.reflect_on_all_associations
Does Testmodel.find(1) return anything?
Try Testmodel.find(1).destroy! (with exclamation point) to see if it tells you anything more.
Problem solved, there was this code added in lib folder which prevents to destroy anything unless I set in model can_destroy to true
thanks all for help
class ActiveRecord::Base
def can_destroy?
false
end
before_destroy do
can_destroy?
end
Related
Problem?
I have a Rails5 application. I have two models. Team and Players.
The association is has_many & belongs_to in between them.
class Team < ApplicationRecord
has_many :players
end
class Player < ApplicationRecord
belongs_to :team
end
Now, I want to perform destroy players before updating the team model. The condition is like below
def update
#team.players.destroy_all if a
........
if b
.... some code.....
elsif c
#team.players.destroy_all
end
if #team.update_attributes(team_params)
redirect_to teams_path
else
... some code..
render :edit
end
end
Note
In team_params, I have players_attributes, so each time if a new entry is there I need to remove all old entries and then #team.update_attributes will insert the entries in players.
Expectations
If #team.update_attributes(team_parmas) fails, then #team.players should be rolled back.
Things I tried
I tried adding Transactions in the first line of update method but it doesn't work.
You can use a gem like paper trail to keep a history, but the nature of rails is to not be able to roll back transactions after committed. One way to mock this would be to keep the attributes in a transient collection of hashes and re-save them if you need the records once more. You could have logic like
team_players = #team.players.map(&:attributes)
Then if you need to 'roll back'
team_players.each do |team_player|
TeamPlayer.create(team_player)
end
This will only work with basic attributes. If you have other model relations you will have to handle them with attributes also.
I have these models:
ofert.rb
class Ofert < ActiveRecord::Base
has_many :purchasing_groups
end
purchasing_group.rb
class PurchasingGroup < ActiveRecord::Base
belongs_to :ofert
has_many :group_goals
end
group_goal.rb
class GroupGoal < ActiveRecord::Base
belongs_to :purchasing_group
end
In a controller action Im trying to do something like this:
copy_of_last_group_goals = #ofert.purchasing_groups.last.group_goals
#ofert.purchasing_groups.create
##ofert.last_purchasing_group.save! adding this, did not work
##ofert.save! This neither work.
#ofert.purchasing_groups.last.group_goals = copy_or_last_group_goals
#ofert.save
The above works great in local and I also try it from the console and it also work. I have my app deployed in heroku with postgresSQL, this is not working in production, it throws this error:
ActiveRecord::RecordNotSaved (Failed to replace group_goals because one or more of the new records could not be saved):
I tried saving the purchasing_groups and also saving the ofert as you could see in the commented lines, but that only worked in the console of my heroku app but not when I try it directly in the deployed app.
Any ideas?
based on me reading these lines of code
copy_of_last_group_goals = #ofert.purchasing_groups.last.group_goals
#ofert.purchasing_groups.create
#ofert.purchasing_groups.last.group_goals = copy_or_last_group_goals
#ofert.save
You want to create a new purchasing_group and fill it with the same group_goals that were the in purchasing_group that came before it? Is that right?
If so, then I would say you can't because you don't have a has_and_belongs_to_many relationship between GroupGoal and PurchasingGroup.
I am creating a record and then pushing the id of the newly created record in a queue, from the after create filter.
From another script I am reading the ids from the queue and reading the db record for the ids instantly.
record = Model.find(id)# this is giving error: Couldn't find record with ID 8732
I am using rails 2.3.14 with mysql2 gem.
What you are experience is known as a race-condition.
Your second script or worker library is trying to access the record before it has been entirely written ("committed"), as ilan pointed out.
A common solution to this problem is using an after_commit callback instead of after_create / after_save etc.
An example from the Article on Rails BestPractices.
Before:
class Notification < ActiveRecord::Base
after_create :asyns_send_notification
def async_send_notification
NotificationWorker.async_send_notification({:notification_id => id})
end
end
class NotificationWorker < Workling::Base
def send_notification(params)
notification = Notification.find(params[:notification_id])
user = notification.user
# send notification to user's friends by email
end
end
After refactoring using after_commit lifecycle hooks:
class Notification < ActiveRecord::Base
after_commit :asyns_send_notification, :on => :create
def async_send_notification
NotificationWorker.async_send_notification({:notification_id => id})
end
end
Further reading: after_commit in the Rails API docs.
Maybe the query result "SELECT * FROM Model WHERE id=8732" is on cache.
You should try to "reload" the query :
record = Model.find_by_id(id, true)
The reason has to do with transaction isolation levels. Although you can read the entry that you just inserted, another process can't until the transaction is committed. This commit happens after the controller returns.
Here is my code:
class UserTopicVisit < ActiveRecord::Base
belongs_to :user
belongs_to :topic
def self.log_last_user_topic_visit(user_id, topic_id)
visit = UserTopicVisit.find_or_create_by_user_id_and_topic_id(user_id, topic_id)
visit.update_attributes!(:last_visit => Time.now.to_formatted_s(:db))
end
end
Which is self-explainig.
The problems is: I need to be able to update object attribute without transaction, by simple mysql query. How can I accomplish that using activerecord?
update_attributes uses a transaction, but oddly, save doesn't, so if you want callbacks and all that jazz without the transaction you can do:
visit.last_visit = Time.now.to_formatted_s(:db)
visit.save!
Under the hood, update_attributes actually just calls attributes= and then save.
Maybe you are looking for update-column
Updates a single attribute of an object, without calling save
Validation is skipped.
Callbacks are skipped.
updated_at/updated_on column is not updated if that column is available.
I have 2 models:
# models/car.rb
class Car < ActiveRecord::Base
belongs_to :model
end
and
# models/manufacturer.rb
class Manufacturer < ActiveRecord::Base
has_many :cars
end
When I'm executing command in rails console Car.find(1).manufacturer it shows me that one more sql query was executed SELECT manufacturers.* FROM manufacturers WHERE manufacturers.id = 54 LIMIT 1,
so I am interested is it usual (for production, first of all) behavior, when a lot of sql queries being executed just to get some object property? what about performance?
UPDATE, ANSWER:
I got an answer from another source: I was told it's "necessary evil" as a payment for abstraction
This is not a "necessary evil" and your intuition that the second query is needless is correct. What you need to do is use :include/includes to tell Rails to do a JOIN to get the associated objects in the same SELECT. So you could do this:
Car.find 1, :include => :manufacturer
# or, in Rails 3 parlance:
Car.includes(:manufacturer).find 1
Rails calls this "eager loading" and you can read more about it in the documentation (scroll down to or Ctrl+F for "Eager loading of associations").
If you always want to eager-load the associated objects you can declare default_scope in your model:
class Car
belongs_to :manufacturer
default_scope :include => :manufacturer
# or Rails 3:
default_scope includes(:manufacturer)
end
However you shouldn't do this unless you really need the associated Manufacturer every time you show a Car record.