Call methods from other controllers in rails - ruby-on-rails

I'm trying to call a method from within another controller and getting a no method error.
So I have two controllers Jobs and Admin, I'm trying to call:
<% #jobs.each do |job| %>
I'm putting this command in the admin contoller's view, within the actual controller file for admin I have:
# GET /jobs
# GET /jobs.xml
# GET /admin
def index
#jobs = Job.all
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #jobs }
end
Which I thought would pull everything I needed over yet I'm still getting a nomethoderror, so how should I include the jobs controller into the admin one?

from the code you posted, it appears that you are not using a query to get the jobs that you want from the database, eg
#jobs = Job.all
Job.all is an activerecord query that gets all the available records from the database. It sounds like you may be confused about how all the MVC pieces are intended to work together.
First, you should not be sharing methods across controllers, in reality your controllers will have very little code.
Rails will route a request, and look for an action defined in the controller, if it doesn't find one, it will go to the views folder and get the template of that action name there, eg a request to Jobs#index will look for an index action in the controller, and then if it isnt there, look for the views/jobs/index.erb template to render. So if you are serving static data and don't need to look up data, you dont even need an action in your controller. Often though, the action is needed because it is in the controller that you do your database and model lookups, and then pass those instance variables to the view. Keep all database queries and model actions out of the views.
def index
#jobs = Job.all
end
and in the view
#jobs.each do |job| ...
The respond_to block is totally unnecessary unless you want to return a differently formatted template than html, like xml or json for a web service.

You have not defined jobs action in Admin controller. You are loading #jobs in index action of Admin controller. If you want this work, rename index action of the Admin controller to jobs and add that route. One way you could add a route is by adding a line in config/routes.rb.
match '/jobs' => 'admin#jobs'
Read through this.

Related

Shared resource for partial in Rails

Premise: I'm quite new to Rails. I'm trying to render a partial in the application layout that will have to display some Event objects. The partial will have to be displayed in every page of the application (it's basically a sidebar). I am aware that I should pass a local variable to the partial, like
<%= render partial: "shared/aside", locals: {events: #events} %>
But this will only work if I define #events in every single controller of the application. Is there a way of setting it globally?
It might be worth noting that events might not be the only resource needed to the partial.
You could do a before action in the application controller. The before action would go at the top of the file, the private method at the bottom
class ApplicationController < ActiveController::Base
# Place at top of file
before_action :set_events
...your other code...
# Place at bottom of file
private
def set_events
#events = Event.all
end

Rails - Partial in another view can't access its controller

I'm trying to build a profile page that displays posts sent only to the requested user, and allows the visitor to write a post of their own. Because this simplified example should have two distinct controllers: users and posts, I made partials for each post action to render within the user's show action.
Directory structure for my views directory looks like this:
- posts
- _index.html.erb
- _new.html.erb
- users
- show.html.erb
... (etc.)
Section that displays these partials within the user's show.html.erb:
<section>
<h3>Posts:</h3>
<%= render '/posts/new', :post => Post.new %>
<%= render '/posts/index', :posts => Post.where(target_id: params[:id]) %>
</section>
I eventually found out that you could pass variables into the partial in this render line, and though this works, it's very messy and probably doesn't follow the best practices.
Ideally, I'd want these partials to be connected with the posts controller so I can write more complex database queries in a place that isn't the view:
class PostsController < ApplicationController
def new
#post = Post.new
end
def index
#posts = Post.where(target_id: params[:id])
end
def create
#post = Post.new(post_params)
#post.user_id = current_user.id
#post.target_id = params[:post][:target_id]
if #post.save
redirect_to :back, notice: 'You published a post!'
else
render new
end
end
private
def post_params
params.require(:post).permit(:body)
end
end
Currently, I haven't found a way of doing this. I know this is a newb question, but thanks for any help in advance.
You are attempting to treat your controllers like models: doing the post work in post controller and the user work in user controller. But controllers are task-oriented, not model-oriented.
Since you want posts info in your user form, it's typical to gather it in the user controller. E.g.
class UsersController < ApplicationController
def show
...
#posts = Post.where(user_id: user.id)
end
end
That #posts instance variable is visible in the show template and any partials it calls. But many coders prefer to send it explicitly through render arguments, as more functional:
<%= render '/posts/post_list', posts: #posts %>
For one thing it's easier to refactor when you can see at a glance all of the partial's dependencies.
I agree somewhat with #Mori's advice. As he said, you are trying to put too much logic into the controller. I think this was a result of you trying to get it out of the view, which is the right idea, but you want business logic to be in the model.
Also, those index and new actions for PostsController are never going to be called. When you are calling the render posts/new for example, that is rendering the view, not the controller action. So, those controller actions have no reason to exist.
I would implement the fix in perhaps a different way than Mori described. It's a recommended practice to try and pass as few instance variables from the controller to the view as possible (see 3rd bullet in the linked section).
Since it's really the show action of the UsersController we are talking about here, I as someone trying to understand your code would assume the instance variable you are passing to the show view is something like #user.
You may want to use an includes method when instantiating the #user object. The includes statement will allow you to load the additional models you will need to instantiate using the minimum number of queries possible (preventing an N+1 query situation). You probably don't want to load every single one if there are thousands of matching posts, so I put an arbitrary limit of 10 on that.
UsersController
def show
#user = User.find(params[:id]).includes(:received_posts).limit(10)
end
#....
View
<section>
<h3>Posts:</h3>
<% unless #user.id == current_user.id %>
<%= render 'posts/form', post: Post.new(user_id: #user.id) %>
<% end %>
<%= render #user.received_posts %>
</section>
Putting the partial for a new post instead as a view called posts/form will allow you to reuse that form if you want to render an edit action (form_for knows which action to use on submit by calling the passed model's persisted? method).
Note that this code assumes the User model has the second relationship with posts set up to be called received_posts, but you can change it to whatever reflects the reality. By passing the received_posts collection to the render method, Rails is smart enough to know that if you want to render a collection of Post models to look for a posts/_post partial and render one for each Post. It's a little cleaner looking IMO. Just make sure to move your posts/show code into that. posts/show implies this is its own action and not something used as a partial for something else.

rails: use jbuilder template in various controller methods

Is it possible to reuse jbuilder-template in another controller method? In other words: how to explicitly say controller method to use concrete jbuilder-template?
From Rails guide.
Rendering an Action's Template from Another Controller.
What if you want to render a template from an entirely different
controller from the one that contains the action code? You can also do
that with render, which accepts the full path (relative to app/views)
of the template to render. For example, if you're running code in an
AdminProductsController that lives in app/controllers/admin, you can
render the results of an action to a template in app/views/products
this way
def my_action
# some code here
render "products/show"
end
def my_action
# some code here
render "products/show.json"
end
or
def my_action
# some code here
render "show.json"
end
without the .json it will try to render an html file.

Rerouting model contents using to_param

I am wanting to expand the URLs associated with the contents of a model called Product, at the moment, I can view a specific product by going to products/ID.
I would like to extend the product URL so it includes some more descriptive information, such as the product name.
I have previously been advised to adjust the to_param function (in Product.rb) as below:
def to_param
"#{id}-#{product_name.parameterize}"
end
However, this doesn't currently work. The URL associated with each product appears correctly when you hover over it / click it, but there is no matching product found. I get the error no match for ID=ID-specific-product-name
If i visit /products/id i can still successfully view the specific item
Can anyone guide me as to how I could generate this longer URL containing the product name (:product_name)?
EDIT
The show controller action in my controller is:
def show
#uniqueturbo = Uniqueturbo.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #uniqueturbo }
end
end
If you're trying to make some SEO friendly urls
http://www.yourdomain.com/products/123123-My-Little-PonyBook
I think that the easiest way is to change the routes, like this
get '/products/:title/:id' => "products#show"
and then you'll get seo-friendly url's like:
http://www.yourdomain.com/products/My-Little-PonyBook/123123
To generate this url, create helper
def url_for_product(product)
"/products/#{product.title}/#{product.id}"
end
The other way is to leave the normal RESTful route, and reparse 'id' parameter, like:
def show
product_id = params[:id].split('_')[0] # :-)
# ...
end
and still you need the helper method, this time, sth like:
def url_for_product(product)
product_path(product) + "_#{product.title.tableize}"
end

Calling methods that render views conditionally in a Rails Controller

I am writing a Ruby on Rails application with a controller called "pages_controller" that is responsible for displaying pages to users. There are 3 different types of pages that can be displayed, and different things have to happen on the back end in each case, so I decided to break the functionality out into 3 methods within the controller. When the user requests a page, the "show" method is called, which figures out whether the page:
1. Belongs to the user
2. Belongs to another user, and can be viewed by the user requesting it
3. Belongs to another user, and cannot be viewed by the user requesting it (unauthorized)
The appropriate method is then called from there to display the page. The code looks something like this:
def show
if (something)
showMine
elsif (something else)
showAnother
else
showUnauthorized
end
end
def showUnauthorized
respond_to do |format|
format.html # showUnauthorized.html.erb
end
end
def showMine
respond_to do |format|
format.html # showMine.html.erb
end
end
def showAnother
respond_to do |format|
format.html # showAnother.html.erb
end
end
I am getting a template missing error because rails wants to render a view when "show" is called, but I do not want any views to be rendered when "show" is called. I simply want "show" to call the correct method from there, and the corresponding view for that method (showMine, showAnother, or showUnauthorized) to be rendered. How can I do this? Or am I going about this the wrong way entirely?
You need to declare these new actions that you have created in the routes file, as they don't belong to the RESTful routes.
I sugest to keep only the show action in your controller and create the IFs in the show view using the render method to include the partials(_showMine.html.erb, showAnother.html.erb, showUnauthorized)
example:
show view:
if (something)
<%= render 'showMine' %>
elsif (something else)
<%= render 'showAnother' %>
else
<%= render 'showUnauthorized' %>
end
I hope it helps...
I basically agree with Samy's comment, but here's some background:
The method that tells Rails what view to use is render. If there's no call to that method in your show method, Rails assumes you have a view called show.xxx.xxx, e.g. show.html.erb, that is supposed to be rendered. Note that it doesn't assume template will be prefixed with show because that's the name of the method. It assumes it will be show because that's the name of the action. The name of the action is passed to the controller as part of the request; it's not simply derived from the name of whatever method has a respond_to block in it.
All the respond_to blocks do is specify different view templates based on the MIME type of the request, but since you never call render, all of those extra methods are still trying to call the show view (show.html.erb in every case), because you never told Rails to render any other view, and the action name is show.
So, instead of the respond_to blocks, just call render [some_view] in each of your other methods.
This might not be the clearest answer, but I'd suggest also reading the following:
http://ryanbigg.com/2009/04/how-rails-works-2-mime-types-respond_to/
It describes what respond_to does, in particular how it keys off the action name to determine what view to render.

Resources