Making a variable available across two controllers - ruby-on-rails

I have a Question model that has_many Answers (a model), each of which belongs_to a Question.
For each Question's show page, e.g., /questions/1, or /questions/2, I would like the user to input an answer.
The form is working, but the problem is that it seems the question variable is not carrying through to the answers controller that the form is posting to... thoughts?
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
#answer = #question.answers.build
end
class AnswersController < ApplicationController
def create
#answer = #question.answers.build
redirect_to 'questions/:id'
end
I'm getting an error: undefined method `answers' for nil:NilClass at the #answer = #question.answers.build in the AnswersController create method.
Is it time to use a global variable?
By the way, short version is that I'm trying to do a Quora/ Stack Exchange clone. Is there a tutorial that's already done this because man that would be amazing...

And so it shouldn't. This is definitely not the place for a global variable either.
When you're creating an answer for a question, it should post to something like /questions/2/answers, which should point to the AnswersController#create action.
In that action, you can load up the correct question record using params[:question_id] (which comes from the URL) and then create the answer for that question.
(you want create and not build in your controller, and also your redirect is incorrect)

Related

Instance variables not picked up in controller

I have a GamesController that has a success method. The method is supposed to find a (game) booking object using a supplied parameter and allow the view to display information relating to the booking object. Doing this gives me an error, suggesting that the #booking instance variable is Nil. My setup is below:
GamesController
class GamesController < ApplicationController
def success
#booking = Booking.find_by(session_id: params[:session_id])
end
end
success.html.erb
<h1>Booking reference: <%= #booking.reference %></h1>
<p>Thank you for your booking</p>
I'm able to work around this by creating a separate method, set_booking, which finds the booking and delivers it to the success function via a before action. My amended setup is:
GamesController
class GamesController < ApplicationController
before_action :set_booking, :only => [ :success ]
def success
end
def set_booking
#booking = Booking.find_by(session_id: params[:session_id])
end
end
success.html.erb (unchanged)
<h1>Booking reference: <%= #booking.reference %></h1>
<p>Thank you for your booking</p>
My question: What I don't understand is, why is the booking object not available in the view without the before_action. I thought that making it an instance variable with '#' would suffice - is there a convention that I'm missing here?
Thank you!
Thank you everyone for your feedback.
I was so confused by the behaviour so I rewrote the controller (yes, it was condensed to make it cleaner). It then worked - but I wasn't fully satisfied because I couldn't find the typo. In short, there wasn't a typo but rather the success function was defined twice 🤦‍♂️ Glad to have figured it out eventually

Rails redirect_to update_path/update_url

In Rails 5, how do you run update on a model, and then execute an update of another controller?
I know it's done with redirect_to, but not sure how exactly. Here is what I have so far:
class BookController < ApplicationController
def update
...
redirect_to author_update_path(id: book.author.id.to_s)
end
end
class AuthorController < ApplicationController
def update
...
redirect_back fallback_location: root_path
end
end
I am on a book page and I updated some fields that belong to the book (like the book title). But then I want to run the update of the author.
I am getting NoMethodError so far...
What do I do?
EDIT
Let me explain it clearly:
My UPDATE action in the authors controller is hundreds of lines long. I need to run it for the nested author. My issue is not in updating the author from within the Books controller update action. My problem is that I need to run the author controller UPDATE action via the UPDATE action of the books controller.
Copy pasting the UPDATE action of the Author controller into the Update action of the Books controller is not an option.
I think that you can do this different, like:
class BookController < ApplicationController
def update
...
#update the author
author = Author.find(book.author.id)
#if you need, you can use Author.update(fields_to_update_with_values)
author.touch
#later redirect
redirect_back fallback_location: root_path
end
end
And you can set in your model associations, an auto "update". RailsGuides say:
If you set the :touch option to true, then the updated_at or
updated_on timestamp on the associated object will be set to the
current time whenever this object is saved or destroyed
Update I recommend that you use a service object and migrate you author controller update code to the service. This is a good practice for easify your controller and helpfull for your issue.
You can follow the Service Objects in Ruby on Rails…and you guide by Rob Race.
For complex actions that need to be shared, I believe the only solution is to extract the actions into a new module. Here is a complete detailed answer for those looking for a similar answer:
class BookController < ApplicationController
def update
# Find the book and then the author of the book
book = Book.find(params[:id])
author = book.author
# create an instance of the author updater and run the update action
author_updater = Modules::AuthorUpdater.new(author)
author_updater.update_author
redirect_back fallback_location: root_path
end
end
class AuthorController < ApplicationController
def update
author = Author.find(params[:id])
# create an instance of the author updater and run the update action
author_updater = Modules::AuthorUpdater.new(author)
author_updater.update_author
redirect_back fallback_location: root_path
end
end
# Place this as author_updater.rb inside lib/modules/
class Modules::AuthorUpdater
def initialize(author)
#author = author
end
def update_author
# update actions go here...
end
end

Having multiple instance variables in rails controller action? (Rails best practices)

Say for example I have two models, posts and category. Now say I want to make it so the from the category show page you can create a new post using the form_for method. To do this, you will obviously need access to the #category variable and a new instance of a post (#post). Is this acceptable code in the controller?
#app/controllers/categories_controller.rb
def show
#category = Category.find(params[:id])
#post = Post.new
end
Or is it bad practice to have two instance variables defined in the one controller action - and if it is, what would be the best practice for a case like this?
I usually do something like:
#app/controllers/categories_controller.rb
helper_method :category
helper_method :post
def show
end
private
def category
#_category ||= params[:id] ? Category.find(params[:id]) : Category.new(params[:category])
end
def post
#_post ||= Post.new(params[:post])
end
Then, in your views, just refer to post or category (not #post or #_post). The nice thing is you can remove the same logic from your new, delete, etc methods...
Actions related to posts should be in the PostsController as much as possible.
Let's say the user is looking at all posts under the category "rails": /categories/rails
There's a button on that page to create a new post under the "rails" category, href: /posts/new?category=rails
This takes you to PostsController#new where you instantiate a new Post, validate the category param and build a view. This view could either be a new page, or a modal popping up.

[Rails3]Not able to call a custom mmethod of post controller outside the post controller

I defined one of my custom method in PostsController as follows:-
class PostsController < ApplicationController
...<<other methods truncated from display >>
public
def update_bid_winner (winner_id)
#post.bid_winner_id = winner_id
#post.save
end
end
But when I try to call it from some other controller (BidsController in my case). Where Bid is a nesteded resource of post:-
resources :posts do
resources :bids do
member do
get 'offer_bid'
end
end
end
I tried to call my custom method as follows from the bids controller :-
def offer_bid
#post = Post.find(params[:post_id])
#bid = Bid.find(params[:id])
#post.update_bid_winner(#bid.user_id) <<<<<<<<<< Here goes the call
#post.save
redirect_to post_path(#post)
end
But I get an error saying that undefined method update_bid_winner :-
undefined method `update_bid_winner' for #<Post:0xb68114f4>
Help me out. am I doing anything wrong here? If so , please suggest ways to achieve the same !!
Thanks in Advance.
This is not working because you are calling the method on a Post object but you have defined the method in the PostsController class. This method must be defined in the Post model file (app/models/post.rb) for it to work as you want.
Generally, methods that update an object should go in that object's respective class.
PostsController and Post are two different classes. Notice how #post is a Post object: #post = Post.find(params[:post_id])
Define the method in app/models/post.rb instead of app/controllers/posts_controller.rb.
Actually the best way to achieve my task is use the following line in the controller itself :-
#post.update_attribute(:bid_winner_id,#bid.user_id)
No need for any new methods in model to update the attribute.
But the inputs provided as other answers really was helpful to enlighten me :). Thanks.

Rendering different controller actions in Rails when using resource-oriented controllers

Say I'm making a Q&A site like StackOverflow. I have two resources: Question and Answer. I'm using default Rails RESTful resource routes, so each resource has its own controller and methods for creating it.
In the /questions/show view, I want to allow the user to submit an answer for the particular question. The form will POST to /answers, which will get routed as a request to the AnswersController with a call to the create method.
If the answer was created, I can simply redirect back to the original question. However, I'm running into trouble dealing with validation failures on the answer object. I need to render the /question/show view and show the validation errors for the answer object. It's not clear to me how to best do this.
Here are example snippets of what the two controllers might look like.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
if #answer.save
redirect_to #answer.question
else
# What should go here??
end
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
#answer = Answer.new(:question_id => #question.id)
end
end
What should go in the else clause of the AnswersController's create method? A redirect seems wrong, since the error is really caused by the same request. Calling something like render :template => 'questions/show' seems wrong too, since I have to initialize any instance variables that the template depends on.
This style of having separate actions for calling GET to view the form for creating an object and calling POST to actually create the object seems to work well within a single controller.
How can it be done across controllers?
Try this on for size. It redirects, but passes back the dodgy answer object full of errors.
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
# stash the dodgy answer if it failed to save
session[:answer] = #answer unless #answer.save
redirect_to #answer.question
end
end
class QuestionsController < ApplicationController
def show
#question = Question.find(params[:id])
# if we have one stashed in the session - grab it from there
# because it probably contains errors
#answer = session[:answer] || Answer.new(:question_id => #question.id)
end
end
Some details need adding (eg clearing it from the session when done) etc

Resources