Best way to group similar actions using Rails Routing? - ruby-on-rails

I want to be able to group similar actions under one controller to keep things neat and clean.
For example, if I have a Game model, and I currently have:
resources :games do
get 'schedule/previous', to: "Games#previous"
get 'schedule/upcoming', to: "Games#upcoming"
get 'schedule/calendar', to: "Games#calendar"
end
These obviously get more and more unweildy, especially because I have a bunch of other actions in my GamesController. How can I move these to a new controller (or otherwise organize them more cleanly)?
If possible I'd like to put the new "Schedule" controller under app/controllers/games/schedule_controller.rb or something like that.
I tried to do this using namespaces, scopes, and resources (and most of the combinations of two of those) and couldn't figure it out.

To achieve this as you want
First modify the routes
routes.rb
resources :games do
get 'schedule/previous', to: "games/schedule#previous"
get 'schedule/upcoming', to: "games/schedule#upcoming"
get 'schedule/calendar', to: "games/schedule#calendar"
end
app/controllers/games/schedule_controller.rb
class Games::ScheduleController < ApplicationController
#Metods here
def previous
end
end
Another option if you are using rails 4 is to use concerns. Its not necessary no modify routes.rb
app/controllers/concerns/schedule.rb
module Schedule
extend ActiveSupport::Concern
#Metods here
def previous
end
end
app/controllers/games_controller.rb
class GamesController < ApplicationController
include Schedule
end

Related

What is the best way to apply a function to a set of routes in rails?

I am looking to create a filtering system where I can apply a set of sanity checks to a subset of routes on a REST API. What is the most railsy way to create the mapping of routes to functions which need to be called on that route? I am looking to create a one to many mapping where a function can be mapped to more than one route. Currently I am creating a module where I am mapping the routes and checking the request after it comes in. Is there a native rails way to do this?
Don't know about best way but one way would be to create a controller with this sanity check and have your controllers inherit from it.
class SanityController < ApplicationController
before_action :sanity_check
def sanity_check
# some world class sanity checks
end
end
class OtherController < SanityController
end
You can also skip certain actions if needed
class OtherController < SanityController
skip_before_action :sanity_check, only: :index
end

Nested controllers class definitions under parent directory

Quick question, I’m building a rails app that has the ability to contain multiple “features” one of those being rsyslog, other features could be any other software. Below is the current construct:
Controllers:
features/
features/rsyslog/
features/rsyslog/rsyslog_inputs_controller.rb
features/rsyslog/rsyslog_outputs_controller.rb
features/rsyslog/rsyslog_rules_controller.rb
Routes:
resources :features do
scope module: "features/rsyslog” do
resources :rsyslog_inputs
resources :rsyslog_outputs
resources :rsyslog_rules
end
end
(controller code does not work here)
Controller code:
class Features::RsyslogInputsController < ApplicationController;end
class Features::RsyslogOutputsController < ApplicationController;end
class Features::RsyslogRulesController < ApplicationController;end
I’m curious, given that there are multiple features and I’d like to keep each features controllers nested in a directory specific to that feature.. i.e. (rsyslog containing all of the rsyslog controllers) as opposed to building the structure like so:
features/
features/rsyslog_inputs_controller.rb
features/rsyslog_outputs_controller.rb
features/rsyslog_rules_controller.rb
How does that translate to the controller code class definition? doing this:
Class Features::Rsyslog::RsyslogInputsController < ApplicationController; end
(note the addition of Rsyslog above)
RubyMine it’s getting pissy with a “expected end of line” on the second set of “::” in the class definition... However, the routes work.. Am I missing something here? is this just my IDE being weird?
app/features/rsyslog/inputs_controller.rb
Should define
class Features::Rsyslog::InputsController < ApplicationController
...
end
Not
class Features::Rsyslog::RsyslogInputsController < ApplicationController
...
end
Then:
scope :features do
scope :rsyslog do
resources :inputs, controller: 'features/rsyslog/inputs'
resources :outputs, controller: 'features/rsyslog/outputs'
resources :rules, controller: 'features/rsyslog/rules'
end
end
Which yields:
inputs GET /features/rsyslog/inputs(.:format) features/rsyslog/inputs#index
POST /features/rsyslog/inputs(.:format) features/rsyslog/inputs#create
new_input GET /features/rsyslog/inputs/new(.:format) features/rsyslog/inputs#new
edit_input GET /features/rsyslog/inputs/:id/edit(.:format) features/rsyslog/inputs#edit
input GET /features/rsyslog/inputs/:id(.:format) features/rsyslog/inputs#show
PATCH /features/rsyslog/inputs/:id(.:format) features/rsyslog/inputs#update
PUT /features/rsyslog/inputs/:id(.:format) features/rsyslog/inputs#update
DELETE /features/rsyslog/inputs/:id(.:format) features/rsyslog/inputs#destroy
outputs GET /features/rsyslog/outputs(.:format) features/rsyslog/outputs#index
POST /features/rsyslog/outputs(.:format) features/rsyslog/outputs#create
new_output GET /features/rsyslog/outputs/new(.:format) features/rsyslog/outputs#new
edit_output GET /features/rsyslog/outputs/:id/edit(.:format) features/rsyslog/outputs#edit
output GET /features/rsyslog/outputs/:id(.:format) features/rsyslog/outputs#show
PATCH /features/rsyslog/outputs/:id(.:format) features/rsyslog/outputs#update
PUT /features/rsyslog/outputs/:id(.:format) features/rsyslog/outputs#update
DELETE /features/rsyslog/outputs/:id(.:format) features/rsyslog/outputs#destroy
rules GET /features/rsyslog/rules(.:format) features/rsyslog/rules#index
POST /features/rsyslog/rules(.:format) features/rsyslog/rules#create
new_rule GET /features/rsyslog/rules/new(.:format) features/rsyslog/rules#new
edit_rule GET /features/rsyslog/rules/:id/edit(.:format) features/rsyslog/rules#edit
rule GET /features/rsyslog/rules/:id(.:format) features/rsyslog/rules#show
PATCH /features/rsyslog/rules/:id(.:format) features/rsyslog/rules#update
PUT /features/rsyslog/rules/:id(.:format) features/rsyslog/rules#update
DELETE /features/rsyslog/rules/:id(.:format) features/rsyslog/rules#destroy

Rails - Add a 'GET' sub-route to a main route

Currently I have a route called requests that may have GET/POST endpoints. But another requirement is to achieve the following format: /api/requests/sync.
I tried the following in routes.rb:
Rails.application.routes.draw do
resources :requests do
get "sync"
end
end
But this gives me the following format:
/requests/:request_id/sync
How can I create a sub-route as requests/sync without having it as a sub-route of /:request_id/sync?
Check out the guide. Specifically, collection routes. You'll do something like:
Rails.application.routes.draw do
resources :requests do
collection do
get "sync"
end
end
end
Which will give you requests/sync
To pick up on the sync_controller question...
Personally, not knowing much about what you're actually up to, I would keep sync as an action on the requests_controller. Something like:
class RequestsController < ApplicationController
...
def sync
...
end
...
end
While sync is not one of the standard RESTful actions, it seems more natural to me than creating a new controller. In general, but not always, I think of controllers as being noun-oriented (e.g., 'request', in your case) and actions being verb-oriented. "Sync" seems way more verb-y to me than noun-y.
You could do something along the lines of what Cyzanfar suggests. But, I would suggest you ask yourself:
Do you need all the standard actions for your would-be sync_controller?
Is there some meaningful reason to inherit from Requests::RequestsController?
Do you even need Requests::RequestsControler or could you just do RequestsController and then have Requests::SyncController inherit from RequestsController (which seems less tortured to me)?
And probably other important questions I'm not thinking about on-the-fly.
Here is another way to achieve this with namespacing your controllers so that you can have a distinct controller for sync and requests where the request controller will act as the parent (base) controller.
routes.rb
namespace :requests do
resources :sync
end
requests/requests_controller.rb
class Requests::RequestsController < ApplicationController
end
requests/sync_controller.rb
class Requests::SyncController < Requests::RequestsController
end
Now you'll have the nested CRUD paths under requests
/requests/sync/new
/requests/sync/index
/requests/sync/create
...

Uninitialized constant "Controller Name"

I'm having an error with my routes/resources and controllers.
I have the following in the routes.rb:
# routes.rb
resources :users do
resource :schedule
end
And I have a schedule_controller.rb inside controllers/users/ set up as I think it should be:
class Users::ScheduleController < ApplicationController
# Controller methods here...
end
Running a rake:routes shows
user_schedule POST /users/:user_id/schedule(.:format) schedules#create
new_user_schedule GET /users/:user_id/schedule/new(.:format) schedules#new
edit_user_schedule GET /users/:user_id/schedule/edit(.:format) schedules#edit
GET /users/:user_id/schedule(.:format) schedules#show
PUT /users/:user_id/schedule(.:format) schedules#update
However, navigating to /users/:user_id/schedule is returning the following error:
uninitialized constant SchedulesController
My only thoughts on what the problem could be are that is has something to do with nested resources or declaring a single resource and I'm going wrong somewhere.
I'm using the helper
new_user_schedule_path(current_user)
when linking to my 'new' view.
It should be SchedulesController, not Users::ScheduleController. Controllers should only be namespaced when the route is namespaced with namespace. Controller names should also always be plural.
What you're creating is a nested resource, not a namespaced one.
Is the namespacing of the SchedulesController intentional? i.e. do you really mean to do this?
class Users::SchedulesController < ApplicationController
Or are you only doing that because schedules are a "sub-thing" from users?
The reason I ask this is because typically within Rails, nested resource controllers aren't namespaced. You would only namespace a controller if you wanted to modify the controllers in a special way under a namespace. A common example of this would be having some controllers under an admin namespace, inheriting from a BaseController within that namespace that would restrict only admins from acessing those controllers.
Option 1
If you didn't intentionally namespace this controller, then you want to remove the Users:: prefix from your controller, and move it back to app/controllers/schedules_controller.rb, the helpers back to app/helpers/schedules_helper.rb and the views back to app/views/schedules. Perhaps you ran a generator which also generated a Users::Schedule model, which should also need to be renamed to Schedule and moved back to app/models/schedule.rb.
Option 2
If you did intentionally namespace this controller, then you want to do this in your routes:
namespace :users do
resources :schedules
end
Leave everything that's been generated as it should be.
In your routes.rb you need to specify the controller like this:
resources :users do
resource :schedules, controller: 'users/schedules'
end
replace resources :users to
namespace :users
Because your schedule controller is inside users folder.
class Users::ScheduleController < ApplicationController
# Controller methods here...
end

Overriding Rails Default Routing Helpers

I'm writing an app where I need to override the default routing helpers for a model. So if I have a model named Model, with the corresponding helper model_path() which generates "/model/[id]". I'd like to override that helper to generate "/something/[model.name]". I know I can do this in a view helper, but is there a way to override it at the routing level?
You can define to_param on your model. It's return value is going to be used in generated URLs as the id.
class Thing
def to_param
name
end
end
The you can adapt your routes to scope your resource like so
scope "/something" do
resources :things
end
Alternatively, you could also use sub-resources is applicable.
Finally you need to adapt your controller as Thing.find(params[:id]) will not work obviously.
class ThingsController < ApplicationController
def show
#thing = Thing.where(:name => params[:id).first
end
end
You probably want to make sure that the name of your Thing is unique as you will observe strange things if it is not.
To save the hassle from implementing all of this yourself, you might also be interested in friendly_id which gives you this and some additional behavior (e.g. for using generated slugs)
You need the scope in routes.rb
scope "/something" do
resources :models
end

Resources