Instance variables not picked up in controller - ruby-on-rails

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

Related

passing parameters between methods of *different* controllers

There are already threads on how to pass parameters between methods of the same controller.
But is there a way of passing parameters between methods of different controllers ?
Because sometimes you have for instance an article for which, when you call the #update action on it, you also wish at the same time to update the tags associated with it over the #update action of the TagsController.
The idiom would be something like to instantiate TagsController in ArticlesController#actions and then pass a new instance of Rack::Response with only a :tags part of the whole params-hash to it while doing tags_controller_instance.send(:update).
You would have to send only special parts of the params-hash which the ArticlesController received because TagsController will have different StrongParameters!
I think it boils down to the question about how to create a Rack::Request that copies the Request of ArticlesController on one hand and how to pass to it not the whole params-hash but only meaningful parts of it.
Then it should be possible to ask for the updated taglist afterwards with this.tags in ArticlesController#update, right?
Thanks
Von Spotz
The idiom would be something like to instantiate TagsController in ArticlesController#actions and then pass a new instance of Rack::Response with only a :tags part of the whole params-hash to it while doing tags_controller_instance.send(:update).
Please don't do that! It will be hard to understand & maintain. There might be other side effects you haven't even thought about too.
The question is, what does the TagsController#update do that you don't want to replicate in the ArticlesController? If it's some complex logic, I think you should abstract this e.g. in a (service) object and call it instead.
Something like this:
class UpdateTags
def self.run(params)
new(params).run
end
def initialize(params)
#params = params
end
def run
# copy logic from TagsController
end
end
and then you can use / reuse this service in your controllers
class TagsController
def update
UpdateTags.run(params)
end
end
class ArticlesController
def update
# update Article or move this in a dedicated service too
UpdateTags.run(params)
end
end
Another approach could be to let Article accept attributes for Tags with nested attributes.
Edit
To elaborate a little bit why instantiating another controller is not a good idea.
What about before filters? Is it fine to execute them (again)?
What about view rendering? You obviously don't want to render views so that's additional work and might have side effects?
Other side effects like caching, logging, data analysis.
Instantiating a controller is not a public API so this can change between Rails versions making an update difficult.
It's not a common pattern so it will be hard to understand
I would just stress again that this is not a good idea to do. Duplicated code is better than the wrong abstraction.
This just sounds like a crazy Wile E. Coyote solution to a problem that's trivial to solve with nested attributes.
The only public methods of a controller in Rails should be the actions of the controller which are the methods that respond to HTTP requests and these should only be called though http calls.
Any other method should be private/protected. There is no valid scenario where you would be calling TagsController#update off another controller to update tags as that method should do only one thing - update tags in response to PATCH /tags/:id.
If you want to update an article and its tags in one single request use accepts_nested_attributes_for :tags:
class Article < ApplicationRecord
has_many :tags
accepts_nested_attibutes_for :tags
end
That creates a tags_attributes= setter that you can use update the nested records together with the parent.
And if you want to share functionality between classes where classical inheritance is not suitable use horizontal inheritance. In Ruby this means modules:
module Taggable
private
def tag_attributes
[:foo, :bar, :baz]
end
end
class TagsController < ApplicationController
include Taggable
# PATCH /tags/:id
def update
#tag = Tag.find(params[:id])
if #tag.update(tag_params)
redirect_to #tag, success: 'Tag updated'
else
render :edit
end
end
private
def tag_params
params.require(:tag).permit(*tag_attibutes)
end
end
class ArticlesController < ApplicationController
include Taggable
def update
if #article.update(article_params)
redirect_to #article, success: 'Article updated'
else
render :edit
end
end
private
def article_params
params.require(:article).permit(:title, :body, tags_attributes: tag_attibutes)
end
end
<%= form_with(model: #article) do |f| %>
...
<%= f.fields_for :tags do |tag| %>
<div class="field">
<%= tag.label :foo %>
<%= tag.text_field :foo %>
</div>
<% end %>
<% f.submit %>
<% end %>
In some cases you might choose to use AJAX instead to create/update/delete a nested resource "on-the-fly" by sending asynchronous HTTP requests which do call another controller (but not in the same request). However this is really out of scope for this question.
If you're using params in many controllers, just add them to private methods in the application controller.

Params Not In Rails Controller Method, but in before filter and application controller

I have controllers that look like this:
class ApplicationController < ActionController::Base
before_filter :print_params
def print_params
p params
end
end
class CommandsController < ApplicationController
before_filter :print_params
def print_params
p params
end
end
class FidelityBondRenewalsController < CommandsController
before_filter :print_params
def print_params
p params
end
def renew_with_changes
p params
end
end
When I run my cucumber tests I see print params the whole way through except for when I finally hit the renew_with_changes method and then my params disappear. I have other methods in this controller and it isn't happening to them. I've searched the code base for any differences between the other methods and this one and couldn't find anything.
If I put a binding.pry in the print_params method in the FidelityBondRenewalsController and hit next over and over again, I eventually get to:
16: def process_action(*args)
17: run_callbacks(:process_action, action_name) do
=> 18: super
19: end
20: end
which has params. If I type next, then I go to my renew_with_changes method and the params are gone.
What can I try to try to track this down? This is a Rails3.2 app, with the strong parameters gem installed to be more like Rails4, if that is useful.
Route information:
resources :fidelity_bond_renewals do
member do
.
.
.
put :renew_with_changes
end
end
The problem was two fold:
For whatever reason Binding.pry could not read the params. I haven't figured out why. I could do p params and see them. I could do a binding.pry right after that and try to p params and they would not print. This is different behavior than I described above. The only change is that I am driving today and my pair is not. We may need to resolve these difference if we can reproduce them. I'll report back on this if he experiences differences.
The params themselves were being manipulated by some code that meant that I needed to access the key differently. Once this was figured out, it all started working.

RoR: instances variables within controller methods

My question is about controller methods (possibly included from an outside class) that work with instance variables. I frequently use a before_filter in controllers to set up certain variables, e.g.:
class DocumentController < ApplicationController
before_filter :fetch_document
def action
#document.do_something
end
private
def fetch_document
#document = Document.find(params[:id])
end
end
I've been working on a project in which a few controllers will share some functionality, say, document editing. My first thought was to extract the relevant methods, and get them from application_controller.rb or a separate module. But then I noticed I was writing code that looks like this:
def fetch_document
#document = Document.find(params[:id])
end
def do_something_to_document
#document.do_something
end
This sets off warning bells: do_something_to_document is essentially assuming the existence of #document, rather than taking it as an argument. Is this, in your sage opinions, a bad coding practice? Or am I being paranoid?
Assuming it is an issue, I see two general approaches to deal with it:
Check for the instance var and bail unless it's set:
def do_something_to_document
raise "no doc!" unless #document
[...]
end
Call the action with the instance var as an argument:
def do_something_to_document(document)
[...]
end
2 looks better, because it hides the context of the calling object. But do_something_to_doc will only be called by controllers that have already set up #document, and taking #document as a method argument incurs the overhead of object creation. (Right?) 1 seems hackish, but should cover all of the cases.
I'm inclined to go with 1 (assuming I'm right about the performance issue), even though seeing a list of methods referencing mysterious instance vars gives me hives. Thoughts? Let me know if I can be more clear. (And of course, if this is answered somewhere I didn't see it, just point me in the right direction...)
Thanks,
-Erik
If you really need document in different controllers, I'd do something like this:
class ApplicationController < ActionController::Base
private
def document
#document ||= Document.find(params[:document_id])
end
end
class FooController < ApplicationController
before_filter :ensure_document, :only => [:foo]
def foo
document.do_something
end
private
# TODO: not sure if controller_name/action_name still exists
def ensure_document
raise "#{controller_name}##{action_name} needs a document" unless document
end
end
As #variable are session/instance variable you will get a Nil exception in do_something_to_document method.
The first code is fine, because before_filter will always load your #document.
I suggest you to write something like that
def fetch_document(doc_id)
#document ||= Document.find(doc_id)
end
def do_something_to_document
my_doc = fetch_document(params[:id])
end
where do_something_to_document is in the controller (if not, dont use params[:id], even if you know you can access this global, use another explicit parameter). The ||= thing, will asssure that you call the base only once by request.

[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.

attributes and constructors in rails

I'm new to rails and don't even know if this is the correct way of solving my situation.
I have a "Club" ActiveRecords model which has a "has_many" association to a "Member" model. I want the logged in "Club" to only be able to administrate it's own "Member" so in the beginning of each action in the "Member" model I did something similar to the following:
def index
#members = Club.find(session[:club_id]).members
to access the right members. This did not however turn out very DRY as I did the same in every action. So I thought of using something equivalent to what would be called a constructor in other languages. The initialize method as I've understood it. This was however not working, this told me why, and proposed an alternative. The after_initialize.
def after_initialize
#club = Club.find(session[:club_id])
end
def index
#members = #club.members
....
does not seem to work anyway. Any pointers to why?
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.members
Makes me think that the #club var isn't set at all.
Also, is this solution really a good one? This makes it hard to implement any kind of "super admin" who can manage the members in all of the clubs. Any ideas on where I am missing something?
You can use a before_filter.
Define the filter in your ApplicationController (so that you can access it from any controller).
class ApplicationController < ActionController::Base
# ..
protected
def load_members
#members = if session[:club_id]
Club.find(session[:club_id]).members
else
[]
end
end
end
Then, load the filter before any action where you need it.
For example
class ClubController < ApplicationController
before_filter :load_members, :only => %w( index )
def index
# here #members is set
end
end
Otherwise, use lazy loading. You can use the same load_members and call it whenever you need it.
class ClubController < ApplicationController
def index
# do something with members
load_members.each { ... }
end
end
Of course, you can customize load_member to raise an exception, redirect the client if #members.empty? or do whatever you want.
You want to use a before_filter for this.
class MembersController < ApplicationController
before_filter :find_club
def index
#members = #club.members
end
private
def find_club
#club = Club.find(session[:club_id])
end
end
I'm a fan of a plugin called Rolerequirement. It allows you to make custom roles and apply them by controller: http://code.google.com/p/rolerequirement/

Resources