Rails 4 Namespace - ruby-on-rails

I am getting a routing error when I attempt to create a new db entry or update a current one.
ERROR: No route matches [POST] "/pubs"
Routes.rb:
resources :people, except: [:show] do
resources :pubs, except: [:create, :new, :edit, :destroy]
end
resources :articles
resources :pubs, except: [:create, :new, :edit, :destroy]
namespace :sekret do
resources :people do
resources :pubs
end
end
sekret/pubs_controller
class Sekret::PubsController < SekretController
def index
#pubs = Pub.all
end
def show
#pub = Pub.find(params[:id])
end
def new
#person = Person.find(params[:person_id])
#pub = #person.pubs.new
end
def create
#pub = Pub.new(pub_params)
if #pub.save
flash[:notice] = "Article created successfully!"
redirect_to sekret_person_pub_path(#pub)
else
render :new, status: :unprocessable_entity
end
end
def edit
#pub = Pub.find(params[:id])
end
def update
#pub = Pub.find(params[:id])
if #pub.update(pub_params)
redirect_to sekret_person_pub_path(#pub)
else
render :edit, status: :unprocessable_entity
end
end
def destroy
pub = Pub.find(params[:id])
pub.destroy
redirect_to sekret_people_path
end
private
def pub_params
params.require(:pub).permit(
:pubmed_id, :journal, :pages, :date, :type, :link, :authors,
:title, :notes, :auth_id, :person_id)
end
end
After going through all of this setup, when I allow the non-namespace pubs to resolve edit, update, etc, the update process goes through without a hitch. Once I limit these functions to within the password protected namespace I get the routing error. After parsing through the routes I can see that sekret_person_pub_path is listed there. I think I am missing something somewhere.
Rake Routes:
pubs#index
pub GET /pubs/:id(.:format) pubs#show
PATCH /pubs/:id(.:format) pubs#update
PUT /pubs/:id(.:format) pubs#update
sekret_person_pubs GET /sekret/people/:person_id/pubs(.:format) sekret/pubs#index
POST /sekret/people/:person_id/pubs(.:format) sekret/pubs#create
new_sekret_person_pub GET /sekret/people/:person_id/pubs/new(.:format) sekret/pubs#new
edit_sekret_person_pub GET /sekret/people/:person_id/pubs/:id/edit(.:format) sekret/pubs#edit
sekret_person_pub GET /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#show
PATCH /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#update
PUT /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#update
DELETE /sekret/people/:person_id/pubs/:id(.:format) sekret/pubs#destroy
sekret_people GET /sekret/people(.:format)

By using resources :pubs, except: [:create, :new, :edit, :destroy], you are preventing the route generation from providing POST /pubs.
The namespace and nested resources will generate a URL POST sekret/people/:person_id/pubs.
In your controller, you should create the Pub as an associated object.
def create
person = Person.find(params[:person_id])
#pub = person.pubs.new(pub_params)
if #pub.save
flash[:notice] = "Article created successfully!"
redirect_to sekret_person_pub_path(#pub)
else
render :new, status: :unprocessable_entity
end
end
If you want to restrict access the create method, you could use an authorization library such as Pundit in which case you would setup a policy to restrict who can do what.
https://github.com/elabs/pundit

You are missing out on the routes because rails form don't use the correct routes when namespacing so you'll have to specify them manually
<%= form for #pub, url: sekret_person_pubs_path do |f| %>
to let the form knows which route to post, if you do not specify the url, rails will use url: person_pubs_path behind the scenes
Edit: forgot to add _path

Related

How do I edit an element from an index page if they are nested in another resource in Rails?

I need to edit the card element from the card index page, on this page I have only one parameter - the card ID. The decrease in the number of parameters was due to rewriting the route for the index page only.
I am new to Ruby and do not quite understand how I can find these parameter(board_id, column_id). Controllers with the function of updating the card are already there. How do I find these options correctly?
I have this routes:
get 'cards' => 'cards#index', as: :cards
resources :boards do
resources :columns, except: [:index, :show, :edit] do
resources :cards, except: [:index, :show]
end
end
end
So to edit the card element I need :board_id, :column_id, cards/:id:
board_column_card_path PATCH /boards/:board_id/columns/:column_id/cards/:id(.:format) cards#update
My cards_controller:
class CardsController < ApplicationController
def new; end
def index
#cards = Card.all.order(created_at: :desc).paginate(page: params[:page])
end
def create
#card = #column.cards.build(card_params)
#card.user = current_user
if #card.save
flash[:success] = "Card was successfully created."
else
flash[:error] = #card.errors.full_messages.join("\n")
end
end
def update
if #card.update(card_params)
flash[:success] = "Card was successfully updated."
else
flash[:error] = #card.errors.full_messages.join("\n")
end
end
Thank you for your help.
As I understand it, you want to update your card, with just your card id? This can be done easily by changing your routes. I recommned using shallow nesting: https://guides.rubyonrails.org/routing.html#shallow-nesting.
# inside your routes.rb file - you can add exceptions
# as in the code you have posted above if you wish
resources :boards do
resources :columns, shallow: true do
resources :cards, shallow: true do
end
end
end
# now because you are using shallow editing you can simply do this:
edit_card_path(#card.id)
And when you want to update, you can do so easily by just using the card_id. You will not need your the board and column ids. It is hard to be more specific because you have not given the card_params nor any forms.

How to keep the rails nested resource route url when model save fails?

Summary:
I have a nested attribute. I go to route:
/customers/:id/credit_cards/new
On the create action, save fails, code does
render :new
This pushes the URL to:
/credit_cards/new
How do I make sure the url stays with the customer route?
Details:
I want to use the following routes:
# Credit cards should be associated with a customer except
# potentially on initial creation:
resources :customers do
resources :credit_cards, only: [:index, :show, :new, :create, :edit, :update, :destroy, :show]
end
# Allow creating a credit card but selecting
resources :credit_cards, only: [:new, :create]
Basically a nested route for when a customer exists and a non-nested route for when I can create and assign a customer in the same view.
I have a single controller at
app/controller/credit_cards_controller.rb
In the new and create action I check if I have a customer ID or not
before_action :set_credit_card, only: [:show, :edit, :update, :destroy]
before_action :set_customer, only: [:index, :show, :create, :new, :edit, :update]
# GET /credit_cards/new
def new
#credit_card = if #customer
#customer.credit_cards.build rescue CreditCard.new
else
CreditCard.new
end
end
def create
#credit_card = CreditCard.new(credit_card_params)
respond_to do |format|
if #credit_card && #credit_card.save
format.html { redirect_to on_new_or_update_redirect_location, notice: 'Credit card was successfully created.' }
else
# HERE IS THE ISSUE: Figure out how to make sure the url stays as /customers/:id/credit_cards/new instead of /credit_cards/new
format.html { render :new }
end
end
end
When there is a validation failure, it re-renders the view, but pushes the URL to
credit_cards/new
So I no longer am in the correct URL and customer_id is no longer a parameter. I assume if I can pass the customer_id it will do the right thing, but I have not found how to do that.
This is probably happening because your form is posting to /credit_cards, rather than to /customers/:id/credit_cards.
Change your form to look something like this:
<%= form_for [#customer, #credit_card] do -%>
... etc...
<%= end %>
That should handle either case - whether the #customer is defined or not. It will then post to customer_credit_cards_path(#customer) if the customer is defined, or credit_card_path if customer is not defined. Be sure to set the #customer variable in your create action. This way when you render :new it will render the form with the #customer variable.

Controller method #show getting called

I have a link on my #index view:
<%= link_to 'Export Calendar (ICS)', { controller: :tickets, action: :ics_export, format: :ics }, class: "class-needed right" %>
routes.rb that pertains to this:
resources :tickets
get 'tickets/calendar' => 'tickets#ics_export'
post 'tickets' => 'tickets#index'
patch 'tickets/:id/close' => 'tickets#close', as: 'close_ticket'
post 'tickets/:id' => 'ticket_comments#create'
My TicketsController that pertains:
before_action :set_ticket, only: [:show, :edit, :destroy, :update, :close]
def show
#ticket_comment = TicketComment.new
end
def ics_export
tickets = Ticket.all
respond_to do |format|
format.html
format.ics do
cal = Icalendar::Calendar.new
tickets.each do |ticket|
event = Icalendar::Event.new
event.dtstart = ticket.start
event.description = ticket.summary
cal.add_event(event)
end
cal.publish
render :text => cal.to_ical
end
end
end
private
def set_ticket
#ticket = Ticket.find(params[:id])
end
And when I click the link, it takes me to /tickets/calendar.ics which is correct but I get the following error:
ActiveRecord::RecordNotFound in TicketsController#show
Couldn't find Ticket with 'id'=calendar
Extracted source (around line #83):
private
def set_ticket
#ticket = Ticket.find(params[:id])
end
The #ticket = Ticket.find(params[:id]) is highlighted. Which make sense that it is failing to call a ticket with an id of calendar.
Request has parameters:
{"id"=>"calendar",
"format"=>"ics"}
How do I fix this error? Why is it calling the show action?
There is a footnote in the canonical Rails Routing from the Outside In to the effect:
Rails routes are matched in the order they are specified, so if you have a resources :photos above a get 'photos/poll' the show action's route for the resources line will be matched before the get line. To fix this, move the get line above the resources line so that it is matched first.
As commented, the fix is to specify get 'tickets/calendar' => ... ahead of resources :tickets. If the order of routes is in question, you can run rake routes, which, to the best of my knowledge, should render your routes in the order they are checked.

Rails 4: One-to-one controller issue

I'm building an app which consists on sharing résumés. I am using Devise gem. Each user is able to create only one résumé. I made the models and and their relations. Resume belongs_to User and User has_one 'Resume'.
After making the views, I wanted to test my app but I got the error: undefined methodbuild' for nil:NilClass`
Here is my ResumeController and my routes.rb
class ResumeController < ApplicationController
before_action :find_resume, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:show]
def show
# #resume = Resume.find_by(params[:id])
end
def new
#resume = current_user.resume.build
end
def create
#resume = current_user.resume.build(resume_params)
if #resume.save
redirect_to #resume, notice: "resume was successfully created"
else
render 'new'
end
end
def edit
end
def update
if #resume.update(pin_params)
redirect_to #resume, notice: "resume was successfully updated"
else
render 'edit'
end
end
def destroy
#resume.destroy
redirect_to root_path
end
private
def resume_params
params.require(:resume).permit(:title, :description)
end
def find_resume
#resume = resume.find(params[:id])
end
end
Routes.rb
Rails.application.routes.draw do
devise_for :users
resources :resume, except: [:index]
get 'static_pages/index'
root to: "static_pages#index"
end
I just want the user to be able to create only one Resume and then he will be able to share it.
Update: After following messanjah's answer there was another error coming from the _form.html.erb: undefined method resumes_path' for #<#<Class:0x00...>. Here is the gist with forms and model: goo.gl/XvW2LH So you can see all the files closely.
Without more knowledge of where the error is happening, I can only suggest some areas that might be suspect.
To build a has_one relationship, you must use the build_*association* constructor.
def new
#resume = current_user.build_resume
end
def create
#resume = current_user.build_resume(resume_params)
end

undefined method `order_path' for #<#<Class:0x00000102efef18>:0x00000100f3c9c8>

This is my controller file:
class OrdersController < ApplicationController
...
def create
#order = current_user.current_cart.order #get a current order
if #order.nil?
#order = current_user.current_cart.build_order #if one does not exist, create it
end
#order.update_attributes!(...) #update the attributes
render :new
end
...
end
I get a "undefined method 'order_path'" error whenever I try to save/update the order model.
This is the simplified version of the view:
<%= form_for #order do |f| %>
....
<% end %>
Whenever #order = Order.new it works, and if I do #order = current_user.current_cart.order, it works. But, as soon as I save something or update something before rendering a template, it gives me the error. I would like to save the model.
In the routes the order is a simple resources :orders.
I have same error. When routes.rb
resources :orders, only: [:index, :create, :edit] do
# ...
end
Solution
add :update
resources :orders, only: [:index, :create, :edit, :update] do
# ...
end

Resources