Rails Nested Ressources and External Get - ruby-on-rails

I have a nested ressources and I created an external get to an alternative "Show" view, like that :
resources :teams do
get "alternativeshow"
resources :teammates
end
That gives me this url which is perfect :
http://appname.com/teams/:team_id/alternativeshow
and call the controller teams#alternativeshow
Inside teams#alternativeshow I want to set #team and #teammates just like in my regular #show
def show
#team = current_crafter.teams.find(params[:id])
#teammates = #team.teammates
end
but I can't figure out how.
When I copy-paste show actions inside alternativeshow I got this error
Couldn't find Team without an ID
I noticed (without understanding why) in rake routes that :id becomes :team_id so I setted #team like this to make it work:
#team = current_crafter.teams.find(params[:team_id])
However I can't find how to have my teammates since the ressource is nested inside teams ?
Is "get" the right way to do it ?
Thank you very much for your help !

Try this,
resources :teams do
resources :alternatives, only: [:show]
resources :teammates
end
This should provide the team_id and id params in the alternatives#show
Edit:
instead of a show action for #teammates in your alternatives#show, you'd want to use an index action to be able to get a collection, so you could add an index to the only: [:index, :show] call
than
alternativescontroller ...
def index
#team = Team.find(params[:team_id]
#teammates = #team.teammates
end
end
All nested resource contollers methods follow this pattern

Related

New action not being reached in my Ruby app

I'm just learning Ruby on Rails and building a fairly simple app that stores info to a rake generated db. I'm trying to add a new action to the controller but I can't trigger it. There's a before_action getting triggered which shouldn't be.
class GamesController < ApplicationController
before_action :set_entry, only: %i[ show edit update destroy ]
before_action :authenticate_user!
def index
end
def analyse
#The function here doesnt matter, its not reaching it because its hitting the set_entry instead
puts 'howdy'
end
private
def set_entry
#entry = Entry.find(params[:id])
end
end
The error I'm hitting is Couldn't find Game with 'id'=analyse", highlighting the set_entry action, which to my understanding should only be running with the show, edit, update, and destroy actions (which are also in the full controller and running fine). There are other actions in the controller (such as create and new) which don't seem to trigger that set_entry and are running just fine as expected. For example, linking to the new path takes me to /entry/new, while linking to an edit path takes me to /entry/:id/edit, which is all fine. But it keeps linking my new analyse action trying for entry/:id/analyse when I want it to go to entry/analyse.
My button to trigger it is simply:
<%= link_to "Analyse", analyse_path %>
Which is in a navbar in my application.html.erb
And here's my routes.rb:
Rails.application.routes.draw do
resources :entry
devise_for :users
resources :users
root to: "entry#index"
get 'entry/index'
get 'entry/analyse', as: 'analyse'
end
The path /entry/analyse is matched two routes:
entry#show action in resources :entry
entry#analyse action in get 'entry/analyse', as: 'analyse'
Because the route matching is interpreted through config/routes.rb in order, and resources :entry is the first route the path is matched. At the result, entry#show action will handle the requests from /entry/analyse.
The solution is simple, just switch the order of resources :entry and get 'entry/analyse', as: 'analyse' in config/routes.rb. For example:
Rails.application.routes.draw do
get 'entry/analyse', as: 'analyse' # <- to here
resources :entry
devise_for :users
resources :users
root to: "entry#index"
get 'entry/index'
# from here ...
end
Move all of your resources to the end of your route configuration. I am not sure why its getting tripped up but it seems something within your routes is matching analyse to your entry resource and routes within rails are matched in order. All custom routes really should come first to prevent something like a generic route catching your expected action.
Best practices also state your root to: should be at the top of the configuration since it is your most popular route generally in an application.

Rails link_to polymorphic parent, which can have a nested route

I have the Comment model, which is polymorphic associated to commentable models like Project, User, Update etc. And I have a page where a user can see every User's comment. I want a link near each comment with an address of an object this comment is associated with.
I could write something like that:
link_to 'show on page', Object.const_get(c.commentable_type).find(c.commentable_id)
But this will work only for not nested routes (like User). Here's how my routes look like:
resources :users do
resources :projects, only: [:show, :edit, :update, :destroy]
end
So when I need a link to a Project page, I will get an error, because I need a link like user_project_path.
How can I make Rails to generate a proper link? Somehow I have to find out if this object's route is nested or not and find a parent route for nested ones
You could use a bit of polymophic routing magic.
module CommentsHelper
def path_to_commentable(commentable)
resources = [commentable]
resources.unshift(commentable.parent) if commentable.respond_to?(:parent)
polymorphic_path(resources)
end
def link_to_commentable(commentable)
link_to(
"Show # {commentable.class.model_name.human}",
path_to_commentable(commentable)
)
end
end
class Project < ActiveRecord::Base
# ...
def parent
user
end
end
link_to_commentable(c.commentable)
But it feels dirty. Your model should not be aware of routing concerns.
But a better way to solve this may be to de-nest the routes.
Unless a resource is purely nested and does not make sense outside its parent context it is often better to employ a minimum of nesting and consider that resources may have different representations.
/users/:id/projects may show the projects belonging to a user. While /projects would display all the projects in the app.
Since each project has a unique identifier on its own we can route the individual routes without nesting:
GET /projects/:id - projects#show
PATCH /projects/:id - projects#update
DELETE /projects/:id - projects#destroy
This lets us use polymorphic routing without any knowledge of the "parent" resource and ofter leads to better API design.
Consider this example:
Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :projects
resources :users do
# will route to User::ProjectsController#index
resources :projects, module: 'user', only: [:index]
end
end
class ProjectsController < ApplicationController
def index
#projects = Project.all
end
# show, edit, etc
end
class User::ProjectsController < ApplicationController
def index
#user = User.joins(:projects).find(params[:user_id])
#projects = #user.comments
end
end
This would let us link to any project from a comment by:
link_to 'show on page', c.commentable
And any users projects by:
link_to "#{#user.name}'s projects", polymorphic_path(#user, :projects)

Routing error for rails - uninitialized constant SubscribersController

I have a Subscriber model that takes in a "phone_number" and a "visit" integer. I have two controllers Subscribers and Visits(super and sub) I have never worked with nested controllers before and I'm having some issues with namespace I believe. Because I getting back the uninitialized constant error. Basically the subscriber controller signs up a subscriber and the visit controller counts the amount of times they've visited by user input of their phone_number. Why am I getting this error? I'll show my code for clarity.
CONTROLLERS
class Subscribers::VisitsController < ApplicationController
def new
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.find_by_phone_number(params[:phone_number])
if #subscriber
#subscriber.visit += 1
#subscriber.save
redirect_to subscribers_visits_new_path(:subscriber)
else
render "new"
end
end
end
class SubscribersController < ApplicationController
def index
#subscriber = Subscriber.all
end
def new
#subscriber = Subscriber.new
end
def create
#subscriber = Subscriber.create(subscriber_params)
if #subscriber.save
flash[:success] = "Subscriber Has Been successfully Created"
redirect_to new_subscriber_path(:subscriber)
else
render "new"
end
end
ROUTES
Rails.application.routes.draw do
devise_for :users
resources :subscribers, except: :show
get '/subscribers/visits/new', to: 'subscribers/visits#new'
root "welcomes#index"
VIEWS
<h1>hey</hey>
<%= form_for #subscriber do |form| %>
<div class="form-group">
<p>
<%= form.label :phone_number %>
<%= form.text_field :phone_number %>
</p>
<% end %>
ERROR
Hmm, my guess is you are trying to route url subscriber/visits/new to new action in VisitsController?How about changing this line:
get '/subscribers/visits/new', to: 'subscribers/visits#new'
to:
namespace :subscribers do
get '/visits/new', to: 'visits#new'
end
Also try to move this block above resources :subscribers, except: :show if you still get the error.
Cheers
You probably do not need to inherit one controller from another. Simply define the controllers as you normally would:
app/controllers/subscribers_controller.rb
class SubscribersController < ApplicationController
# methods for Subscribers
end
in app/controllers/visits_controller.rb
class VisitsController < ApplicationController
# methods for Visits
end
Note that these must to be located in separate files, so that Rails can find the correct source file by the name of the object that it's looking for. This is a Rails naming convention.
Regarding your routes, you'll need to change to use one of 4 route formats. Reading the section on Adding More RESTful Actions in the Rails Routing from the Outside In guide might help.
1) To route visits as a nested resource, which is what it appears you're actually trying to do, you would use this:
resources :subscribers, except: :show do
resources :visits
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/:id/visits/new
POST /subscribers/:id/visits
GET /subscribers/:id/visits
GET /subscribers/:id/visits/:id
GET /subscribers/:id/visits/:id/edit
PATCH /subscribers/:id/visits/:id/update
DELETE /subscribers/:id/visits/:id/destroy
This is the typical route structure for nested resources and separate controllers.
2) To make visits#new a simple collection (non-member) action in the VisitsController, then you likely want this:
resources :subscribers, except: :show do
collection do
get 'visits/new', to 'visits#new'
post 'visits', to 'visits#create'
end
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/visits/new
POST /subscribers/visits
This is typically used to add new top-level routes in an existing resource and controller.
3) To construct visits as member actions, use this:
resources :subscribers, except: :show do
member do
get 'visits/new', to 'visits#new'
post 'visits', to 'visits#create'
end
end
This will produce these routes:
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
GET /subscribers/:id/visits/new
POST /subscribers/:id/visits
This is normally used to add new member routes in an existing resource and controller.
4) To simply make visits routes appear to be included in subscribers, you could use this:
get '/subscribers/visits/new', to: 'visits#new'
post '/subscribers/visits', to: 'visits#create'
resources :subscribers, except: :show
This will produce these routes:
GET /subscribers/visits/new
POST /subscribers/visits
GET /subscribers/new
POST /subscribers
GET /subscribers
GET /subscribers/:id/edit
PATCH /subscribers/:id/update
DELETE /subscribers/:id/destroy
This may be used to make arbitrary routes appear to be included in an existing resource, when they really may be independent.

Index being redirected to Show View

When I go to /relationships/index it displays the show page even if it is not mentioned in the controller? Then when I try to just go to an index view without a show page created I get the following error: Unknown action: The action 'show' could not be found for relationships controller, even with no mention of it in the controller or a view file for the action.
routes.rb
Mymanual::Application.routes.draw do
resources :validation_rules
resources :validations
resources :product_types
resources :products
resources :connections
resources :relationships
root :to => 'products#index'
end
relationships_controller.rb
class RelationshipsController < ApplicationController
def index
end
def new
#relationship = Relationship.new
end
end
Then just HTML in a index.html.erb, show.html.erb and new.html.erb file.
It's because /relationship path is for relationships#index. If you go to /relationships/index, Rails router assumes you want to go to relationships#show path with params[:id] == 'index'.
Even though you want the index action, you don't enter index in the url. Otherwise, it thinks you're trying to find a Relationship with an id of index.
So just go to /relationships instead.
Go to a console and then run:
rake routes
It will give you all routes your app understand
In some place of this routes you will have something like:
relationships relationships#index /relationships
relationship relationships#show /relationships/:id
The issue that you have is that the app understand /relationships/index as relationships with id=index
The fix? use /relationships url

Rails - how to route to a nested resource when containing resource is not set

I know that in a nested resource, you have to pass the id of the containing resource for it to work.
However, I want to allow users to create a review for a product and specify the product within the form in the new view rather than actually pass it in in the route. So rather than being on a product and then review it, there would be a master review form where you can select any product within the form.
I am getting an error trying to do this and can't figure it out - what is the solution?
#error
No route matches {:action=>"new", :controller=>"reviews"}
#view
new_product_review_path
#controller
def new
##product = Product.new
#review = Review.new
end
#routes.rb
resources :products do
resources :reviews
end
Keep reviews nested within products, but move the creation for reviews outside:
# routes.rb
resources :products do
resources :reviews, except: [:new, :create]
end
resources :reviews, only: [:new, :create]
Then use new_review_path to create a new review.
If you wanted to have the option to create a review either attached to a particular product or not, drop the :except option for your nested review, and modify your controller action and form based on product_id.present?.
EDIT: (In response to the Zephyr's follow up question)
In the example above, I've specified that the :new and :create should only be accessible outside products, and not inside it by using the :except and :only options for reviews -- feel free to drop them if you'd like to be able to do anything with reviews regardless of whether it's nested or not. The only thing to be careful of then is that the same controller actions will handle the requests regardless of whether it's nested or not -- reviews#index will handle both /reviews and /products/1/reviews, for example -- so you'll need to make sure that your controller handles that properly. For example, using a before filter:
# users_controller.rb
before_filter :filter_by_product
def filter_by_product
#parent_product = Product.find(params[:product_id]) if params[:product_id]
#reviews = #parent_product ? #parent_product.reviews : Review.all
end
def new
#review = #reviews.build
end
...
You may also need to make changes to your view based whether it's nested or not. For example, in your form to create a review, you could use a drop down of products if it's not nested or replace that with a hidden field with the product_id if it is.
You can use namespace thing to wrap your route with something else
Move resources :reviews outside the block
resources :products
resources :reviews
and use
new_review_path
This way, your reviews are not tied down to any product.

Resources