Rails add relative record after save - ruby-on-rails

I have a Rails 3.2 app which contains the following models:
class Costproject < ActiveRecord::Base
has_one :costquestion, :dependent => :destroy
class Costquestion < ActiveRecord::Base
belongs_to :costproject
When I create a costproject, I want one related costquestion created.
In the cosproject model, I tried:
after_save :create_costquestion
protected
def create_costquestion
self.costquestion.build
end
But, self seems to be nil - why is that?
undefined method `build' for nil:NilClass
Thanks for the help!

As other points out the reason, why the error, I wouldn't re-iterate it. And here is a solution you can use :
def create_costquestion
self.build_costquestion
self.save!
end
Check the helper methods comes with this associations from Rails.
Note: your method create_costquestion is what Rails already created for you by its magic. So, better give it a new name which shouldn't overwrite the out of the box method.
Edit: A little change to save the child while saving parent.
class Costproject < ActiveRecord::Base
has_one :costquestion, :dependent => :destroy, autosave: true

costquestion is nil this point. You're pointing to a relationship that doesn't yet exist. If you changed this to be something like this you should be ok:
def create_costquestion
costquestion = Costquestion.build #does this save?
self.costquestion = costquestion
self.save
end

Related

How to identify newly added has_many association in rails after_commit

Have below association in author class
has_many :books,
class_name :"Learning::Books",
through: :elearning,
dependent: :destroy
with after_commit as,
after_commit :any_book_added?, on: :update
def any_book_added?
book = books.select { |book| book.previous_changes.key?('id') }
# book's previous_changes is always empty hash even when newly added
end
Unable to find the newly added association with this method. Is this due to class_name?
Rails has a couple methods that might help you, before_add and after_add
Using this, you can define a method to set an instance variable to true
class Author < ApplicationRecord
has_many :books, through: :elearning, after_add: :new_book_added
def any_book_added?
#new_book_added
end
private
def new_book_added
#new_book_added = true
end
end
Then when you add a book to an author, the new_book_added method will be called and you can at any future time ask your Author class if any_book_added?
i.e.
author = Author.last
author.any_book_added?
=> false
author.books = [Book.new]
author.any_book_added?
=> true
As you can see, the callback method new_book_added can accept the book that has been added as well so you can save that information.

Cannot modify association ":has_many." using Ruby on rails

I'm working with three tables as follows:
article.rb
class Article < ActiveRecord::Base
has_many :comments
has_many :comentarios, :through => :comments
end
comment.rb
class Comment < ActiveRecord::Base
belongs_to :article
has_many :comentarios
end
and comentario.rb
class Comentario < ActiveRecord::Base
belongs_to :article
end
Everything works fine until I attempt to add a 'comentario' and returns this error
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection in ComentariosController#create
Cannot modify association 'Article#comentarios' because the source reflection class 'Comentario' is associated to 'Comment' via :has_many.
This is the code I use to create a new 'comentario'
comentarios_controller.rb
class ComentariosController < ApplicationController
def new
#comentario = Comentario.new
end
def create
#article = Article.find(params[:article_id])
#comentario = #article.comentarios.create(comentario_params)
redirect_to article_path(#article)
end
private
def comentario_params
params.require(:comentario).permit(:comentador, :comentario)
end
end
The output returns an error in the line where I create #comentario from calling #article but I can't see why since Ruby documentation says that once I associate comentario to article using :through, I can simply call something like #article.comentario.
Any idea of what is causing this error?
or do you have any suggestion on how to achieve this association in any other way?
Ok. The issue is that Rails is confused about which article to use here.
Your Comment model belongs_to :article but also your Commentario belongs_to :article... so if you use #article.commentarios - it's confused as to whether the article refers to the article of the comment or the article of the commentario.
You will probably need to update your form to be more explicit about what you're referring to. A form for the commentario should actually include fields for the comment it creates.
Somebody else had the same problem here. You may wish to look at the solution here: "Cannot modify association because the source reflection class is associated via :has_many"

before_destroy for model that destroys dependent records

I have a situation like this:
class Shop < ActiveRecord::Base
has_many :services, dependent: :destroy
end
class Service < ActiveRecord::Base
has_many :model_weeks, dependent: :destroy
end
class ModelWeek < ActiveRecord::Base
before_destroy :prevent_destroy, if: :default?
private
def prevent_destroy
false
end
end
When I try to destroy a shop, I get ActiveRecord::RecordNotDestroyed: Failed to destroy the record because it starts destroying the associated records first, and it gets prevented by the callback in the ModelWeek.
I could easily unset default for ModelWeek when destroying a shop, only if I could catch it. before_destroy in the Shop model does not get triggered prior to when the above mentioned exception is raised.
So, is there a way to catch this in Shop model, or if not, is it possible to "know" in the ModelWeek that destruction was triggered by a parent? I investigated parsing the caller, but it offers nothing useful, and it would be messy anyway...
i solved this problem by using a funny hack,
you just need to put the before_destroy line
before the association line
and it will run before_destroy before deleting the associations
Since rails has destruction chain from related children to parent, it really makes sense, right? To make it easy, we can overwrite the destroy method in ModelWeek like this:
class ModelWeek < ActiveRecord::Base
# before_destroy :prevent_destroy, if: :default?
def destroy
unless default?
super
end
end
end
After some research and testing, this is what I came up with:
This is the order in which the methods are being called (do_before_destroy are any methods specified in the before_destroy callback):
Shop.destroy
Service.destroy
ModelWeek.destroy
ModelWeek.do_before_destroy
Service.do_before_destroy
Shop.do_before_destroy
So, I can deal with anything preventing the destruction of a child (ModelWeek) in the destroy method of a parent (Shop):
# Shop
def destroy
# default is a scope
ModelWeek.default.where(service_id: self.services.pluck(:id)).each do |m|
m.unset_default
end
super
end
After that nothing prevents destruction of the child and the chain continues unprevented.
UPDATE
There is even better, cleaner solution, without a need for overriding the destroy of the parent and doing any queries:
class Shop < ActiveRecord::Base
has_many :services, dependent: :destroy, before_remove: :unset_default_week
private
def unset_default_week(service)
service.model_weeks.default.unset_default
end
end
The modern solution is to use a before_destroy callback in the parent model with prepend: true option, making the callback trigger before the destruction callback that's defined under the hood by dependent: :destroy on association.
class Service < ActiveRecord::Base
has_many :model_weeks, dependent: :destroy
before_destroy :handle_model_weeks, prepend: true
private
def handle_model_weeks
# special handling here
end
end
See also a Rails thread on this.

Rails4: undefined method `committed? for Model Object

I am working on a Rails 4 application and recently got into a strange issue. I am looking for your help here. Kindly advise.
A small gist snippet has been created to understand the issue undefined method committed?
Just to summarize everything:
# app/models
class User < ActiveRecord::Base
has_many :responses, dependent: :destroy
end
class Response < ActiveRecord::Base
has_one :report
has_many :points
belongs_to :user
end
class Report < ActiveRecord::Base
belongs_to :response
end
class Point < ActiveRecord::Base
belongs_to :response
end
# config/routes.rb
resources :users do
resources :responses do
resources :action_plans
end
end
# app/controllers/action_plans_controller.rb
class ActionPlansController < ApplicationController
before_filter :response
def new
#report = #response.build_report
5.times do
#response.points.build
end
end
private
def response
#response = current_user.responses.find(params[:id])
end
end
Whenever, I am trying to hit:
http://localhost:3000/users/:user_id/responses/:id/action_plans/new
I get error that says: undefined method `committed?' for Response Object. What I am doing wrong here?
By defining a method called response in your controller you're overriding an internal getter used by Rails. To solve the problem, just use a different name for your before action. The common way of naming the action is to use set_<entity name> so set_response it is.
There is a Response class namespaced inside ActionDispatch (ActionDispatch::Response) and it is used throughout Rails. Can it be the case that you're actually hitting the response object instead of your model? Maybe use pry-rails to debug it from inside?

Rails 3, Modeling Question

rails 3 newbie, using Devise for auth...
I want to create the following models:
class Instance < ActiveRecord::Base
has_many :users
has_many :notes
end
class User < ActiveRecord::Base
belongs_to :instance
end
class Note < ActiveRecord::Base
belongs_to :instance
end
To create a new note in the notes_controller.rb
def create
#note = instance.notes.build(params[:note].merge(:instance_id => current_user.instance_id))
end
But I'm getting the following ERROR: "undefined local variable or method `instance' for #"
Ideas?
You haven't assigned anything to "instance" yet, so there's nothing to reference. If you know the instance record already exists in the database, you could do something like:
#instance = current_user.instance
#note = Note.create(:instace_id => #instance.id)
If not, you'd need to check and create it first if necessary, using the same kind of syntax.

Resources