I am trying to learn how to use routes and paths in Rails 4.
I have a model called organisation requests.
I have routes in by routes.rb as follows:
resources :organisation_requests do #, only: [ :index, :new, :create ]
member do
put "requested" => "organisation_requests#requested", as: :request_approval #what does this mean?
put "approved" => "organisation_requests#requested", as: :approved #what does this mean?
put "rejected" => "organisation_requests#rejected", as: :not_approved #what does this mean?
put "removed" => "organisation_requests#removed", as: :removed #what does this mean?
end
end
In my organisation requests controller, I have:
def approved
organisation_request = OrganisationRequest.find(params[:id])
authorize #organisation_request
if organisation_request.state_machine.transition_to!(:approved)
flash[:notice] = "You've been added as a member. Welcome aboard."
format.html { redirect_to :index }
# format.json { render :show, status: :ok, location: #project }
# redirect_to action: :show, id: project_id
# add mailer to send message to owner that article has been approved
else
flash[:error] = "You're not able to manage this organisation's members"
redirect_to(profile_path(current_user.profile))
# redirect_to action: :show, id: project_id
end
end
In my organisation requests index, I'm trying to make a path that allows a user to approve a request:
<% #organisation_requests.each do |orgReq| %>
<tr>
<td>
<%#= link_to orgReq.profile.user.full_name, organisation_request.profile_path(organisation_request.profile.id) %>
</td>
<td>
<%= orgReq.created_at.try(:strftime, '%e %B %Y') %>
</td>
<td>
<%= orgReq.current_state %>
</td>
<td>
<% if policy(orgReq).approved? %>
<%= link_to "APPROVE", request_approval_path(#organisation_request), :class=>'btn btn-info', method: :put %>
<% end %>
</td>
</tr>
<% end %>
When I save all this and try it, I expect the approve button to work. Instead I get an error that says:
undefined method `request_approval_path' for #<#<Class:0x007fa470f72968>:0x007fa474d17a98>
I'm not sure where I'm going wrong?
That's because the method name is "request_approval_organisation_request". You're calling that route insine a controller. If you add
get 'exit', to: 'sessions#destroy', as: :logout
Inside your resources :organisation_requests you will get
logout_organisation_request_path
and so on
rake in Rails 4 has a routes command that will allow you to see what your url path helpers are all named as a result of the contents of your routes.rb file.
From your project root, what does rake routes | grep request_approval yield?
Related
I'm building my second-ever basic Ruby on Rails application and having fun doing it, but have gotten stuck at precisely the same place that gave me trouble (and was never solved) on my last effort: the PUT or PATCH request.
My application has two models: entries and users. A logged-in user should be able to edit only those entries that were originally created by that user.
CONTROLLER
class EntriesController < ApplicationController
# authenticate user (Devise)
before_action :authenticate_user!, :except => [:index, :show]
# set entry upon page load
before_action :set_entry, :only => [:show, :edit, :update, :destroy]
# GET request - display all entries
def index
#all_entries = Entry.all
end
# GET request - display an individual entry
def show
# nothing required here because entry identified with before_action :set_entry on line 2 above
end
# GET request - access form to create a new entry
def new
#entry = Entry.new
#user = User.find(current_user[:id])
end
# GET request - access form to update an existing entry
def edit
if #entry[:user_id] != current_user[:id]
redirect_to root_path
else
redirect_to edit_entry_path
end
end
# POST request - make a new entry/save new data into db
def create
user = current_user[:id]
Entry.create({
entry_title: params[:entry][:entry_title],
book_title: params[:entry][:book_title],
text: params[:entry][:text],
img_url: params[:entry][:img_url],
tag: params[:entry][:tag],
created_at: params[:entry][:created_at],
user_id: user
})
redirect_to entries_path
end
# PUT request - save changes to an existing entry
def update
if #entry.update(entry_params)
redirect_to entry_path
else
render :new
end
end
# DELETE request - delete an existing entry from db
def destroy
#entry.destroy
redirect_to entries_path
end
private
def set_entry
#entry = Entry.find(params[:id])
end
def entry_params
params.require(:entry).permit(:email, :text, :tag)
end
end
VIEW (show.html.erb - shows a single entry and includes links allowing the logged-in user who originally authored the entry to edit or delete it)
<h3>Selected Entry</h3>
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6">
<div>Entry title: <%= #entry.entry_title %></div>
<div>Book title: <%= #entry.book_title %></div>
<div>Text: <%= #entry.text %></div>
</div>
<div class="col-md-4">
<div><%= #entry.created_at.strftime("%b %d, %Y") %></div>
<div>Submitted by: <i><%= #entry.user.email %></i></div>
<div>File under: <i><%= #entry.tag %></i></div>
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
</div>
</div>
ROUTES.RB - At first my routes were the commented-out lines, but then I had a thought that was either madness or sudden realization - should only the GET routes lead with "get"? So that's the non-commented-out attempt you see. Somehow the app works (except for the issue at hand) both ways.
In researching I've come across routes defined using a much more elaborate syntax than that I'm using here. I've been unable to figure out whether a given way of doing things is different convention, outdated, or just inadequate to the task.
Rails.application.routes.draw do
devise_for :users
resources :entries
# root 'entries#index'
# get '/entries' => 'entries#index'
# get '/users' => 'users#index'
# get '/entries/:id' => 'entries#show'
# get '/entries/:id' => 'entries#update'
# get '/entries/new' => 'entries#new'
# get '/entries/:id/edit' => 'entries#edit'
# get '/users/:id' => 'users#show'
# get '/about' => 'pages#index'
root 'entries#index'
get '/entries' => 'entries#index'
get '/entries/new' => 'entries#new'
post '/entries' => 'entries#create'
get '/entries/:id' => 'entries#show'
get '/entries/:id/edit' => 'entries#edit'
put '/entries/:id' => 'entries#update'
delete '/entries/:id' => 'entries#destroy'
get '/users' => 'users#index'
get '/users/:id' => 'users#show'
get '/about' => 'pages#index'
end
Thanks in advance for any insight. If additional context is needed I'm happy to provide.
Edited to add:
PARTIAL (_form.html.erb)
<div class="row">
<div class="col-md-2"></div>
<div class="col-md-6" id="form-container">
<%= form_for #entry do |form| %>
<br>
<%= form.text_field :entry_title, :size => 59, :placeholder => "Entry Title"%>
<br><br>
<%= form.text_field :book_title, :size => 59, :placeholder => "Book Title"%>
<br><br>
<%= form.text_field :img_url, :size => 59, :placeholder => "Image URL"%>
<br><br>
<%= form.text_area :text, :placeholder => "Text" %>
<br><br>
<%= form.text_field :tag, :placeholder => "Tag" %>
<br><br>
<%= form.submit %>
<% end %>
</div>
<div class="col-md-4"></div>
</div>
To edit a record you
first, should use a GET request to get the edit form
second, should submit that form using a PUT/PATCH request
To get to the edit form you should link to the edit path for your entry
<%= link_to 'Edit', edit_entry_path(#entry) %>
The Rails form helpers will automatically set the form to submit with the proper method, PUT OR PATCH.
:method in link_to helpers refers to HTML verb (get, post, etc), while controllers methods naming convention is action.
link_to
You need something as
<%= link_to 'Edit', #entry, :method => 'put' %>
or
<%= link_to 'Edit', #entry, :action => 'update' %>
At a glance you are trying to post with the edit link. Remember new/edit are get methods to render form, so just just delete method part in your links. Like from
<%= link_to 'Edit', #entry, :method => 'update' %>
to
<%= link_to 'Edit', edit_entry_path(#entry) %>
I'm building my second-ever basic Ruby on Rails application
Congrats! You need at least 3 more before it all starts to make sense
To add to the existing answers, you'll be best looking at the resources directive to clean the routes up:
#config/routes.rb
root 'entries#index'
devise_for :users
resources :entries
resources :pages, only: [:index], path_names: { index: "about" }
resources :users, only: [:index,:show]
--
A logged-in user should be able to edit only those entries that were originally created by that user.
This is known as authorization.
Authentication = is user logged in?
Authorization = can user do this?
Although people confuse Devise with being able to handle authorization, it only handles authentication. Whilst you have a simple implementation of this in your controller, you should check out either the CanCanCan or Pundit gems:
#Gemfile
gem "cancancan"
#app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, Post, user_id: user.id
end
end
#app/controllers/entries_controller.rb
class EntriesController < ApplicationController
def edit
authorize! :edit, #entry
end
end
--
Finally, to answer your question directly, you're calling the update method (which doesn't exist) to access the edit view:
<% if current_user %>
<%= link_to 'Edit', #entry, :method => 'update' %>
<%= link_to 'Delete', #entry, :method => 'delete' %>
<% end %>
You should read up about http verbs - this is what the "method" option invokes with the link. As mentioned above, you don't need to set the method for edit as it uses GET. Update uses put/patch, which I can explain later.
A much better way to achieve what you want would be the following:
<%= link_to "Edit", edit_entry_path(#entry) if can? :edit, #entry %>
<%= link_to "Delete", #entry, method: :delete, if can? :destroy, #entry %>
The above uses the CanCanCan authorization method can?
I have users that have posts.
<p id="notice"><%= notice %></p>
<h1>Listing Posts</h1>
<table>
<thead>
<tr>
<th>Comment</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% #posts.each do |post| %>
<tr>
<td><%= post.content %></td>
<td><%= link_to 'Show', post %></td>
<td><%= link_to 'Edit', edit_post_path(post) %></td>
<td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<br>
<%= link_to 'New Post', new_user_post_path %>
And in controller
def destroy
#user = #post.user
#post.destroy
respond_to do |format|
format.html { redirect_to user_posts_url(#user), notice: 'Post was successfully destroyed.' }
format.json { head :no_content }
end
end
What's the proper way to implement a link and controller action to destroy all posts for a particular user?
Edit:
config/routes.rb
resources :users do
resources :posts, shallow: true
end
Edit 2:
resources :users do
#resources :posts, shallow: true
resources :posts, shallow: true do
delete :destroy_all, on: collection
end
end
gives no block given (yield) error
aww my bad.. Just found the error.. forgot to add : to collection
I would pass an array of post IDs only if selected posts need to be deleted. If you want to delete all posts for a particular user, then here's how I would approach it:
config/routes.rb
resources :users do
resources :posts do
delete :destroy_all, on: :collection
end
end
Here, on: :collection means that the route applies to the collection of posts; the route therefore looks like this:
/users/:user_id/posts/destroy_all
You can read more about adding member and collection routes in the Rails Guides:
http://guides.rubyonrails.org/routing.html#adding-more-restful-actions
app/controllers/posts_controller.rb
def destroy_all
user = User.find(params[:user_id])
user.posts.destroy_all
# redirect somewhere
end
app/views/posts/index.html.erb
<%= link_to(
"Delete all posts!",
destroy_all_user_posts_path,
method: :delete
) %>
If you want to delete all posts for the current_user, modify like so:
config/routes.rb
resources :posts do
delete :destroy_all, on: :collection
end
app/controllers/posts_controller.rb
def destroy_all
current_user.posts.destroy_all
# redirect somewhere
end
app/views/posts/index.html.erb
<%= link_to(
"Delete all posts!",
destroy_all_posts_path,
method: :delete
) %>
Hope that helps.
I would create a separate controller method that accepts an array of post ids.
posts_controller.rb
def destroy_all
posts = Post.where(:id => params[:post_ids])
posts.delete_all
redirect_to :back
end
You will also need to supply the ids to the view method.
posts_controller.rb
def index
...
#posts_ids = Post.find(... how ever you need to select all posts...).pluck(:id)
...
end
views/posts/index.html.erb
...
<%= link_to destroy_all_posts_path(:post_ids => #posts_ids), :method => :destroy %>
...
You will also need to supply the route.
routes.rb
resources :users do
resources :posts
delete :destroy_all
end
end
And that should be it :)
You can use:
def destory_posts(user)
user.posts.destroy_all
render :nothing => true
end
add this method to your routes file.
Create a link like destory_posts_path(current_user) from where you want to delete the posts.
I am a newbie to ruby on rails. I was working on a project and run into an issue with a form. I am using devise for authentication. I have a user class which has admin and user roles. The devise generated add/ update methods for user is working properly. I am running into a 'No route matches [PATCH]' error when I am trying to create an edit page for the admins. here is the form I am using
<h4>Update Profile</h4>
<%= form_for #user, :url => {:controller => "admin", :action => "update" } do |f| %>
<%= hidden_field_tag(:id, #user.id) %>
<table>
<tr>
<td>First Name</td>
<td><%= f.text_field :first_name , :class => "form-control"%></td>
</tr>
<tr>
<td>Last Name</td>
<td><%= f.text_field :last_name , :class => "form-control"%></td>
</tr>
<tr>
<td>Email</td>
<td><%= f.text_field :email , :class => "form-control"%></td>
</tr>
<tr>
<td></td>
<td><%= f.submit "Update", :class => "btn btn-md btn-success pull-right" %></td>
</tr>
</table>
<%end%>
This is my controller method
def edit
end
def update
#user = User.find(params[:id])
if request.post?
if(#user.update_attributes(params[:first_name, :last_name, :email] ))
redirect_to :action => "admin_portal"
else
render :action => "edit"
end
end
end
I also have the route
get 'admin/update'
get 'admin/edit'
Can anyone suggest how I can fix this issue.
The point is: you are setting only GET from HTTP's methods, and for updates, you need a PUT or a PATCH method.
There are some conventions when to use PUT or PATCH, but in your case, making a PATCH route would solve your problem as you said
patch 'admin/:1'
But, apparently you are writing yourself a route for every REST method, and Rails has a "helper" structure called resources that create all the REST methods for your.
You could create just one entrance on your config/routes.rb like:
resources :admins
and it would generate every route intended for the REST methods pointing for your user_controller and renamed as admin. Putting only that line of code, is be equivalent to write all these commands on your config/routes:
get 'admins', controller: 'admins', action: :index
get 'admin/:id', controller: 'admins', action: :show
get 'admin/new', controller: 'admins', action: :new
get 'admin/:id/edit', controller: 'admins', action: :edit
post 'admin', controller: 'admins', action: :create
patch 'admin/:id', controller: 'admins', action: :update
put 'admin/:id', controller: 'admins', action: :update
delete 'admin/:id', controller: 'admins', action: :delete
You can see more on Rails guides. It has many useful advices on creating routes.
It's because you have form_for #user for persisted model it generates patch, and you only have get in routes. Change get to patch. More info http://guides.rubyonrails.org/routing.html
Now I am trying to set a form on the page of index.html.erb(members). I want the form to show the result of searching and want it not to move to other pages. I want to show the result on index.html.erb(members) itself.
But the code below makes the application move to show.html(members) after pushing the search button. I have no idea why this happens. Could you give me some advice?
Now I am trying to set search window based on my textbook. But I failed with the error message below. I don't understand the cause although this is just a syntax error. Could you give me some advice?
☆index.html.erb(members controller)
<h1>Listing users</h1>
<p>※登録されているメンバーのリストです。</p>
<%= form_tag :action => 'index' do %>
<div class = "field">
<%= label_tag 'place', '活動場所:' %><br />
<%= text_field_tag 'place' %>
</div>
<%= submit_tag '検索' %>
<% end %>
<%= #places_field %>
☆members controller
def index
if !checklogin? then return end #
#members = Member.all
#places_field = Member.where("place = ?", params[:place])
respond_to do |format|
format.html # index.html.erb
format.json
end
end
☆routes.rb
MiniSNS::Application.routes.draw do
resources :group_message_comments
resources :group_messages do
resources :group_message_comments
end
root :to => 'members#login'
match '/groups/join'
resources :group_messages
resources :groups do
resources :group_messages
end
match '/members/new'
resources :index
resources :groups
post 'groups/:id' => 'group#show'
post '/groups/new'
post '/index/index'
match '/members/login'
match '/members/logout'
match '/members/friend'
match '/members/show'
post '/messages/comment'
resources :comments
resources :messages
resources :friends
resources :members
#OmniAuth
match "/auth/:provider/callback" => "sessions#callback"
match "/logout" => "sessions#destroy", :as => :logout
match '/auth/failure', to: redirect('/')
First, you have to adding remote => true on your submit_tag like:
<%= submit_tag '検索', :remote => true %>
Then on your controller, adding return to js with:
respond_to do |format|
format.html # index.html.erb
format.json
format.js
end
Create new view called index.js.erb and fill that file with Javascript code to update your place field, the simplest way is like:
On index.html.erb make sure you have this kind of code:
<div id="place_field_section"><%= render 'place_field' %></div>
Of course you should have a partial called _place_field.html.erb for example:
<table>
<tr>
<th>Foo</th>
<th>Bar</th>
</tr>
<% #place_fields.each do |place_field| %>
<tr>
<td><%= place_field.foo %></td>
<td><%= place_field.bar %></td>
</tr>
<% end %>
</table>
then on your index.js.erb:
$("#place_field_section").html("<%= escape_javascript(render('place_field')) %>");
Is this what you intent to? Please correct me if I'm wrong.
Having a bit of trouble with the following ruby on rails code snippet - I have books and would like to update a book's name, here is how I try to do it:
<h1>Edit the book "<%= #book.name %>"</h1>
<%= form_tag action: :update, id: #book do %>
<p>
<label for="book_name">Name</label>
<%= text_field :book, :name %>
<%= submit_tag 'Save changes' %>
</p>
<% end %>
This is what I do in the books controller:
def edit
#book = Book.find(params[:id])
end
def update
#book = Book.find(params[:id])
if #book.update_attributes(params[:book])
redirect_to :action => 'show', id => #book
else
#subjects = Subject.find(:all)
render :action => 'edit'
end
end
These are my routes:
root to: 'authors#index'
resources :books, :authors
When I click the submit button in the form, it gives me No route matches [POST] "/books/5" and directs to localhost:3000/books/5 instead of staying and localhost:3000/books/5/edit
What am I doing wrong here? Shouldn't there be a put method for updating stuff somewhere rather than a post method?
Updates should use put not post.
<%= form_tag( { :controller => :books, :action => :update, :id => #book.id }, { :method => :put } ) do %>
or better yet use form_for
<%= form_for #book do |f| %>
On your console run "rake routes" and it will print out all available routes.
Please try this:
We need to specify match in routes file.
match "/books/:id" => "books#update"
resources :books should do the job. you dont have to explicitly use "match".
def edit
#book = Book.find(params[:id])
end
form.html
form_for #book, :method => :put do |f|
def update
#book = Book.find(params[:id])
#book.update_attributes(params[:book])
end
this should do the job.
I had this issue before. Everything was right but still getting the error then I found out it was
gem 'rails-api'
Removed it and it all worked fine.