I am looking to find the best way to setup the routes for my app.
The application allows users to register, post jobs and apply for jobs. The issue I am having is that two routes that should really show different things are linking through to the same pages.
My routes are as follows:
resources :users do
resources :apps
end
resources :jobs do
resources :apps
end
As a result of this I end up with two paths:
users/id/apps
jobs/id/apps
What I would like to do is use the first path to show job applications that a user has completed. I would then like to use the second path to show the owner of the job the applications they have received for that job.
The issue I am having is that both paths end up at apps#index
Any advice people can offer on how to best route this would be much appreciated! :)
Both those paths are potentially fine for what you want to do. When a request comes into
users/:user_id/apps
then you'll be able to do something like this in your controller to populate the list of apps:
#apps = User.find_by_id(params[:user_id]).apps
And in the case of the other path, you'll do the same but with params[:jobs_id], e.g.
#apps = Job.find_by_id(params[:job_id]).apps
In your controller you would have some conditional code that builds #apps depending on which path the request came in via (by looking to see if :user_id or :job_id is in params)... something like this:
if params[:user_id]
#apps = User.find_by_id(params[:user_id]).apps
elsif params[:job_id]
#apps = Job.find_by_id(params[:job_id]).apps
end
or maybe refactored to...
if params[:user_id]
#user = User.find(params[:user_id])
#apps = #user.apps
elsif params[:job_id]
#job = Job.find(params[:job_id])
#apps = #job.apps
end
If you use the "resources" keyword, rails will just map to the default routes.
If you would like specific routes to map to something else, you should consider using "match."
For example:
match "users/id/apps" => "users/id/apps"
match "jobs/id/owners" => "jobs/id/owners"
This page shows a more detailed usage of it: http://guides.rubyonrails.org/routing.html
You also have the options to change the route to something else or the code itself.
Hope that helps.
Related
Is it possible for the route below to dynamically select different controllers or at least for a single controller to dynamically call another controller?
get '*path' => 'routing#show
For example:
/name-of-a-person => persons#show
/name-of-a-place => places#show
I recall reading something about Rails 5 that would enable this but I can't find it again to save my life. It's possible I imagined it.
Another options is to have a RoutingController that depending on which path is received will call different controllers.
The use case is I have URLs in the database with a type, and the controller depends on what type is the URL. I'm thinking something like this:
get '*path' do |params|
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
'persons#show'
when 'place'
'places#show'
end
end
I post my second best solution so far; still waiting to see if anyone knows how to do this efficiently within the routes.
class RoutingController < ApplicationController
def show
url = Url.find_by!(path: params[:path])
url.controller_class.dispatch('show', request, response)
end
end
Hat tip to André for the idea.
You could define one controller and inside its action make something like this:
def generic_show
url = Url.find_by!(path: params[:path])
case url.type
when 'person'
controller = PersonController.new
controller.request = request
controller.response = response
controller.show
when 'place'
...
end
end
However, I would recommend you to move the code you want to reuse to other classes and use them in both controllers. It should be easier to understand and maintain.
I think you may be able to do it using advanced routing constraints.
From: http://guides.rubyonrails.org/routing.html#advanced-constraints
If you have a more advanced constraint, you can provide an object that responds to matches? that Rails should use. Let's say you wanted to route all users on a blacklist to the BlacklistController. You could do:
class BlacklistConstraint
def initialize
#ips = Blacklist.retrieve_ips
end
def matches?(request)
#ips.include?(request.remote_ip)
end
end
Rails.application.routes.draw do
get '*path', to: 'blacklist#index',
constraints: BlacklistConstraint.new
end
I don't think the Rails guide example is particularly good, because this problem could essentially be solved in your application controllers before_action.
In this example, the constraint is used for IP filtering, but you could also implement matches? to check if it's a person. I would imagine something like
def matches?(request)
Person.where(slug: request.params[:path]).any?
end
And as such, the Rails router can decide whether or not to dispatch the request to the persons#show action.
I have the following routes in my config/routes.rb file:
resources :employees do
get 'dashboard'
get 'orientation'
end
employees refers to a regular resource handling the standard RESTful actions. dashboard and orientation are what I currently refer to "custom actions" which act on Employee instances. I apologize if I have my terminology mixed up and dashboard and orientation are really something else. These custom actions respond to URLs as follows:
http://myhost/employees/1/dashboard
i.e. They're "member" actions much like show, edit etc.
Anyway, this all works well enough. Regular actions such as show on EmployeesController obtain the ID of the associated Employee through params[:id]. However, with this current structure, dashboard and orientation have to use params[:employee_id] instead. This is not too difficult to deal with, but does lead to some additional code complexity as my regular before_filters which expect params[:id] don't work for these two actions.
How do I have the routing system populate params[:id] with the ID for these custom actions in the same way as show etc.? I've tried various approaches with member instead of get for these actions but haven't got anything to work the way I would like yet. This app is built using Ruby on Rails 3.2.
This might help you:
resources :employees do
member do
get 'dashboard'
get 'orientation'
end
end
and the above will generate routes like below, and then you will be able to use params[:id] in your EmployeesController.
dashboard_employee GET /employees/:id/dashboard(.:format) employees#dashboard
orientation_employee GET /employees/:id/orientation(.:format) employees#orientation
I haven't tested this example, but you can set the resourceful paths explicitly.
Something like this might work:
resources :employees, path: '/employees/:id' do
get 'dashboard', path: '/dashboard'
get 'orientation', path: '/orientation'
end
Ok, so - What did I do wrong? I feel like I'm missing something very simple...
REQUESTS belongs to USER
USER has many REQUESTS
I'm logged in as current_user (id=3) and want to list my requests:
<%= link_to("My Requests", user_requests_path(current_user)) %>
That link goes to /users/3/requests, but it shows ALL requests, not just those belonging to me.... ???
routes.rb
resources :users do
resources :requests
end
rake routes:
user_requests GET /users/:user_id/requests(.:format) requests#index
This isn't related to your routes, then, it's a scoping problem on your ActiveRecord query. You probably have something like the following in RequestsController:
def index
#requests = Request.all
end
But what you need to have is something more like the following:
def index
#requests = current_user.requests
end
If your Request resource can be accessed independently of users (i.e. there's a use-case for Request.all or /requests) you should actually do a separate namespaced controller (e.g. Users::RequestsController) to handle user-specific requests. Your routes will then need to specify the namespace for the user-specific requests as well.
StackOverflow seems to have this style of routes for questions:
/questions/:id/*slug
Which is easy enough to achieve, both in routes and to_param.
However, StackOverflow seems to also redirect to that path when just an ID is passed.
Example:
stackoverflow.com/questions/6841333
redirects to:
stackoverflow.com/questions/6841333/why-is-subtracting-these-two-times-in-1927-giving-a-strange-result/
Same goes for any variation of the slug
stackoverflow.com/questions/6841333/some-random-stuff
Will still redirect to the same URL.
My question is: Is this type of redirection typically handled in the controller (comparing the request to the route) or is there a way to do this in routes.rb?
The reason I wouldn't think this possible in the routes.rb file is that typically, you don't have access to the object (so you couldn't get the slug based off the ID, right?)
For anyone interested, Rails 3.2.13 and also using FriendlyID
Ok, so I think I've got this.
I was looking into doing something with middleware, but then decided that's probably not the place for this type of functionality (since we need to access ActiveRecord).
So I ended up building a service object, known as a PathCheck. The service looks like this:
class PathCheck
def initialize(model, request)
#model = model
#request = request
end
# Says if we are already where we need to be
# /:id/*slug
def at_proper_path?
#request.fullpath == proper_path
end
# Returns what the proper path is
def proper_path
Rails.application.routes.url_helpers.send(path_name, #model)
end
private
def path_name
return "edit_#{model_lowercase_name}_path" if #request.filtered_parameters["action"] == "edit"
"#{model_lowercase_name}_path"
end
def model_lowercase_name
#model.class.name.underscore
end
end
This is easy enough to implement into my controller:
def show
#post = Post.find params[:post_id] || params[:id]
check_path
end
private
def check_path
path_check = PathCheck.new #post, request
redirect_to path_check.proper_path if !path_check.at_proper_path?
end
My || in my find method is because in order to maintain resourceful routes, I did something like...
resources :posts do
get '*id' => 'posts#show'
end
Which will make a routes like: /posts/:post_id/*id on top of /posts/:id
This way, the numeric id is primarily used to look up the record, if available. This allows us to loosely match /posts/12345/not-the-right-slug to be redirected to /posts/12345/the-right-slug
The service is written in a universal fashion, so I can use it in any resourceful controller. I have't found a way to break it yet, but I'm open to correction.
Resources
Railscast #398: Service Objects by Ryan Bates
This Helpful Tweet by Jared Fine
I have a resource named Cimgs
You can see them in the #index
Them can be searched by content, via Sphinx
Also can be searched by tags
And can be searched by another related Cimg, checking for tags collisions
And here is my question: which is the better way to route this?
Should I pass an extra parameter to the #index and search accordingly to the parameter? After all they all use the same view.
Or should I create an action for each search method?
Or maybe a whole resource?
I'm currently doing the following:
resources :cimgs, path: 'pics' do
collection do
get 'search(/:q)', action: :index, search_by: :content, as: :search_by_content
get 'tags/:tag', action: :index, search_by: :tags, as: :search_by_tag
get 'page/:page', action: :index
end
member do
get 'related', action: :index, search_by: :related, as: :search_by_related
end
end
root :to => 'cimgs#index'
The problem is that I'm using Kaminari for paging, and it doesn't detect the page/:page for another route than the root. (I don't actually know the mechanism that it uses to detect the page/:page)
And here is my controller action:
def index
#cimgs = case params[:search_by]
when :content; Cimg.content_search params[:q], params[:page], request.remote_ip
return redirect_to #cimgs[0] if #cimgs.length == 1
when :tag; Cimg.search_by_tag params[:tag], params[:page]
when :related; Cimg.search_related_to params[:id], params[:page]
else; Cimg.get_for_front_page params[:page]
end
render :index
end
I would keep them all in a single action. My assumption is that eventually, you'll decide it's useful to combine your search methods ("I want an image that's related to this other image, but has the tag 'ketchup'."). That'll be easy to do if you have a single action that handles all your searches already, but much more difficult - or at least involving lots of duplicate code - if you have separate actions.
I do something similar in a site I'm working on. I take a master search hash as params[:search], and then have each individual search term in that hash. And then, since I've made a named_scope (might be a different name in Rails 3) for each search option, I can do something like:
scope = MyModel.scoped
params[:search].each_pair do |key, value|
scope = scope.send(key, value)
end
#models = scope.all
Plus some basic error checking, but that's the idea.
Hope that helps!