Pass data from index method of one controller to create of another - ruby-on-rails

I generated a devise model User, and I also have a Conversation controller. I'm showing all users, except the logged in one, and I'm trying to create a new Conversation between user1 and user2, but I get redirected to the index method of the Conversation Controller, not the create one. I understood from this link that making a post from one controller to another is a bad idea Rails: How to POST internally to another controller action?.
I've also tried to make a send_message method inside the Users controller and define it as a post in routes, but I get redirected to the show method of the Users controller.
What is the clean way of doing this?
class UsersController < ApplicationController
before_action :authenticate_user!
def index
#users = User.where.not(id: current_user.id)
end
def send_message
# #conversation = Conversation.new(conversation_params)
# if #conversation.save
#
# end
end
end
index.html.erb
<div class="col-xs-12 col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<h1> User's index </h1>
<table class="table table-bordered table-hover">
<thead>
<tr>
<th>Email</th>
<th>Created</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% #users.each do |user| %>
<tr>
<td><%= user.email %></td>
<td><%= time_ago_in_words(user.created_at) %> ago</td>
<td>
<div class="btn-group">
<%= link_to 'Send', conversations_path(sender_id: current_user.id, recipient_id: user.id) %>
</div>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
Edit:
private
def conversation_params
params.require(:conversation).permit(:sender_id, :recipient_id)
end
<ActionController::Parameters {"_method"=>"post", "authenticity_token"=>"394MDmcVVelccU//8ISYeqmk146exYc6G7SrrAhbCA/yQ/K8KTpSn/0EkXlZ4hB/g==", "recipient_id"=>"1", "sender_id"=>"3", "controller"=>"conversations", "action"=>"create"} permitted: false>

By default link_to helper sends GET request. You can do it with adding method: :post to its options.
<%= link_to 'Send', path, method: :post %>

You could redirect to new_converstion_path instead of conversations_path. And link by default sends GET not POST request.

Related

devise admin user approval setup

Seems like the tutorial located here has a couple omissions:
When creating the admin accessible controller method, they don't specify which custom devise controller to use, or which base controller to inherit from. So I've placed my code in a PagesController:
class PagesController < ApplicationController
def approve_users
if current_user.admin?
if params[:approved] == "false"
#users = User.find_by_approved(false)
else
#users = User.all
end
end
end
end
The view code that allows you to switch between all users and all unapproved users results in a NoMethodError in Pages#approve_users: undefined method 'each' for User whenever you select to show the users for whom :approved => false. I know why noMethodErrors spring up in app development, and would normally be able to wrap my head around why I'm getting this error. It works when #users = User.all, but not when #users = User.find_by_approved(false)
<% if current_user.admin? %>
<h2>Users</h2>
<%= link_to "All Users", :action => "approve_users" %> | <%= link_to "Users awaiting approval", :action => "approve_users", :approved => "false" %>
<div class="ui form">
<table>
<thead>
<tr scope="col">
<th>First name</th>
<th>Last name</th>
<th>E-mail</th>
<th>Approve</th>
</tr>
</thead>
<% #users.each do |user| %>
<tbody>
<tr>
<td><%= user.firstname %></td>
<td><%= user.lastname %></td>
<td><%= user.email %></td>
<td class="ui checkbox">
<input type="checkbox" tabindex="0" class="hidden">
</td>
</tr>
</tbody>
<% end %>
</table>
</div>
<% end %>
The wiki says it that it provides a simple way to approve users, but their view code actually just provides a simple way to list all users. I'm assuming that I need to use a form helper.
I made a custom admin controller specifically for an admin control panel and put all of the tools in an index page. You can probably do it a number of ways, though.
The common consensus on this seems to be that if you switch #users = User.find_by_approved(false) to #users = User.where(approved: false), it works better. That's what I currently have and it works very well.
I had this problem as well, and I ended up scrapping devise and making a custom user sign in method. You should be able to make it work, though. I followed the tutorial here and it helped immensely. Basically, you want to create a method which will approve users in your admin controller. This is the one I used:
`
def approve
User.where(id: params[:user_id]).update_all(approved: true)
redirect_to admin_index_path
end
From there, you add a put method to your routes.
put 'approve_admin', to: "admin#approve", as: :approve_admin
Finally, wrap your list of users in a form tag and add a hash of all of the user IDs you want to update as a hash.
<%= form_tag(approve_admin_path, method: :put) do %>
<% for user in #unapproved_users %>
<tr>
<td class="mdl-data-table__cell--non-numeric"><%= check_box_tag "user_id[]", user.id %></td>
<td><%= user.name %></td>
<td><%= user.email %></td>
</tr>
<% end %>
</tbody>
</table>
<%= submit_tag "Mark as approved", class: 'mdl-button mdl-js-button mdl-button--raised approve' %>
<% end %>
I made a method for approving users and another for unapproving users so I switched up the #users.each iteration in favor of a for iteration. I added #unapproved_users to my index method to make this work properly. Hope this works for you!

What is the proper way to organize two different views of the same model?

ROR newbie. I have read a lot, but I think I'm slow to absorb. I'm trying to create a web app with two different views of the same content:
A page full of artworks for visitors to browse by image (at the root of the site).
A page with a table listing out those same artworks and showing all the relevant attributes I've defined for them (medium, etc.) so I can review data associated with each artwork. This is supposed to be my super-lightweight admin page.
My questions are:
Since I generated this site from a scaffold, I'm currently using views/artworks/index.html.erb as the admin table view (supposed to be private), and I created a 'home' page and added some code to the 'home' view (and pages_controller) to make the artworks show up there (for the public). Am I doing this right? Is this a good method of organization?
I've installed devise and successfully hidden links for editing artworks on the page behind a sign in, but I don't know the proper way to cordon off portions of the app (e.g. the admin table page, edit pages, etc.) from the rest of the world. Should I be putting these pages in a different folder? How do I make these pages private?
Here are some snippets:
home.html.erb
<div id="artworks" class="transitions-enabled">
<% #artworks.each do |artwork| %>
<div class="box">
<%= link_to image_tag(artwork.image.url(:medium)), artwork %>
</div>
<% end %>
</div>
artworks/index.html.erb:
<div class="row">
<div class="col-xs-6 col-sm-8 col-md-4">
<h1>Listing artworks</h1>
</div>
<div class="col-xs-4 col-sm-2 col-md-2 col-md-offset-6">
<%= link_to 'New Artwork', new_artwork_path, class:"btn btn-default" %>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>Image</th>
<th>Title</th>
<th>Genre</th>
<th>Category</th>
<th>Medium</th>
<th>Date</th>
<th>Dimensions</th>
<th>Availability</th>
<th>Rating</th>
<th>Edit</th>
</tr>
</thead>
<tbody>
<% #artworks.each do |artwork| %>
<tr>
<td><%= link_to image_tag(artwork.image.url(:thumb)), artwork %></td>
<td><%= artwork.title %></td>
<td><%= artwork.genre %></td>
<td><%= artwork.category %></td>
<td><%= artwork.medium %></td>
<td><%= artwork.date %></td>
<td><%= artwork.height %> x <%= artwork.width %></td>
<td><%= artwork.availability %> </td>
<td><%= artwork.rating %> </td>
<td><%= link_to 'Edit', edit_artwork_path(artwork) %></td>
<td></td>
</tr>
<% end %>
</tbody>
</table>
</div>
both pages_controller.rb and artworks_controller.rb have code like this:
def index
if params[:tag]
#artworks = Artwork.tagged_with(params[:tag])
else
#artworks = Artwork.all
end
end
pages_controller.rb:
def home
#artworks = Artwork.all
end
and the routes.rb:
get 'tagged' => 'artworks#index', :as => 'tagged'
devise_for :users
# get "artworks" => "artworks#index"
# post "artworks" => "artworks#index"
root "pages#home"
get "about" => "pages#about"
#get "users" => "users#index"
resources :artworks
Thanks in advance for your patience and help!
This can be the right way. But you have installed devise and hidden the links. But someone can directly hit the url. You need to add some authorization code which will take care that only admin user can edit the artworks. This can be done in many ways. If you have a simple application you can just add a before filter in which you can check if the user is admin then only he can access the page and call that on edit and update action. Either you can also use any authorization gem like Authority or so if it needs many roles. Hope this helps.
For making the code DRY:
before_filter :set_artworks, :only => [:home, :index]
private
def set_artworks
#artworks = Artworks.all
end
artworks/index.html.erb
<div class= 'row'>
<!-- your other source code goes here -->
</div>
You want this page to be private until the user is logged in. Then you can try this :
<% if current_user.present? %>
<div class= 'row'>
<!-- your other source code goes here -->
</div>
<% end %>
This div will be visible only if the user is logged in. for not yet signed in users this page will be private.
In order to make the edit option private :
<% if current_user.present? %>
<div id="artworks" class="transitions-enabled">
<% #artworks.each do |artwork| %>
<div class="box">
<%= link_to image_tag(artwork.image.url(:medium)), artwork %>
</div>
<% end %>
</div>
<% end %>
In any case if you want to public/private few sections then just wrap it inside the conditional if statements for views.
Still if anyone wishes then he can hit the url and enter into your action directly, in that case you can put condition in the controller.
before_action :authenticate_user!, only: [:action_1, :action_2]
Further if you need to keep things private on basis of user role, you can
always chose for cancan gem.
The simplest way of maintaining privileges is using cancancan gem.
Install it via bundler and generate model via rails generate cancan:ability. Then you have to distinguish admin users from others (e.g you can add integer "role" to User model) and write something like this in models/ability.rb#initialize
if user.admin?
can :manage, :all
else
can :read, Artwork
end
and modify controllers like this:
def index
authorize! :edit, Artwork
if params[:tag]
#artworks = Artwork.tagged_with(params[:tag])
else
#artworks = Artwork.all
end
end
This will throw an exception if not admin user will want to go to index. You can use redirect_to to inform user about that error. Add this to ApplicationController:
rescue_from CanCan::AccessDenied do |exception|
redirect_to root_url, :alert => exception.message
end

Rails 4 Restaurant order system ActiveRecord::RecordNotFound in ItemsController#create

I am creating a simple restaurant ordering system with a one to many relationship between a menu and its items. One menu has many items. For the sake of simplicity it is not a many to many.
I am able to create menus fine, but would like to be able to add and show the menu items for that menu in the menus show action.
The menu show action displays okay but when I try to add a new menu item I get the following error:
ActiveRecord::RecordNotFound in ItemsController#create
Couldn't find Menu with 'id'=
raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
And here is the queries from the terminal:
Started POST "/items" for ::1 at 2015-01-11 16:09:44 +0000
Processing by ItemsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"***", "item"=>{"name"=>"Test", "price"=>"23", "course"=>"Main", "vegetarian"=>"1", "allergy"=>""}, "commit"=>"Add item"}
Menu Load (0.3ms) SELECT `menus`.* FROM `menus` WHERE `menus`.`id` = NULL LIMIT 1
Completed 404 Not Found in 8ms
EDIT: I followed doon's advice and looked into nested resources and this is indeed a better way of doing it. The updated code is below:
routes.rb
resources :menus do
resources :items
end
menus_controller.rb
def show
#menu = Menu.find(params[:id])
#items = #menu.items
end
items_controller.rb
def create
#menu = Menu.find(params[:menu_id])
#item = #menu.items.create!(item_params)
if #item.save
flash[:success] = "Item added!"
redirect_to #menu
else
flash[:danger] = "Errors found!"
redirect_to #menu
end
end
private
def item_params
params.require(:item).permit(:name, :price, :course, :vegetarian, :allergy, :menu_id)
end
And the Menus
show.html.erb
<%= link_to "<< Back", menus_path, data: { confirm: back_message } %>
<h1><%= #menu.name %> menu</h1>
<center><button id="toggleButton" class="btn btn-sm btn-info">Show/Hide Add Item Form</button></center>
<br>
<div class="row">
<div class="col-xs-offset-3 col-xs-6 toggleDiv hideDiv">
<%= form_for [#menu, Item.new] do |f| %>
<table class="table table-condensed table-no-border">
<tr>
<th scope="row" class="col-xs-2">Name:</th>
<td class="col-xs-10"><%= f.text_field :name %></td>
</tr>
<tr>
<th scope="row">Price:</th>
<td><%= f.text_field :price %></td>
</tr>
<tr>
<th scope="row">Course:</th>
<td><%= f.select(:course, options_for_select([['Starter', 'Starter'], ['Main', 'Main'], ['Dessert', 'Dessert'], ['Drink', 'Drink']]), prompt: "Please select...", class: 'form-control') %></td>
</tr>
<tr>
<th scope="row">Vegetarian:</th>
<td><%= f.check_box :vegetarian %></td>
</tr>
<tr>
<th scope="row">Allergy:</th>
<td><%= f.text_field :allergy %></td>
</tr>
<tr><td colspan="2"><%= f.submit "Add item", class: "btn btn-sm btn-success col-xs-offset-4 col-xs-4" %></td></tr>
</table>
<% end %>
</div>
</div>
<table class="table table-condensed">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Course</th>
<th>Vegetarian</th>
<th>Allergy</th>
</tr>
</thead>
<tbody>
<% #items.each do |item| %>
<tr>
<td><%= item.name %></td>
<td><%= number_to_currency(item.price, unit: "£") %></td>
<td><%= item.course %></td>
<td><%= item.vegetarian %></td>
<td><%= item.allergy %></td>
</tr>
<% end %>
</tbody>
</table>
What I would probably do in a case like this (especially if your item doesn't exist outside of a menu) is to use Nested Resources:
http://guides.rubyonrails.org/routing.html#nested-resources
http://railscasts.com/episodes/139-nested-resources (a bit dated, but still a decent grounding)
Below are some changes to get you pointed in this direction.
config/routes.rb
resources :menu do
resources :items
end
now our urls will look like
/menu/:menu_id/items/
So we need to adjust the items_controller to get the menu by looking at :menu_id, and we don't need the hidden field anymore. I put it in a before_action as every method in the controller will build through the association.
items_controller.rb
class ItemsController < ApplicationController
before_action :find_menu
...
def create
#item = #menu.items.new(item_params)
if #item.save
redirect_to #menu, notice: 'item added'
else
redirect_to #menu, warning: 'item failed'
end
end
...
private
def find_menu
#menu = Menu.find(params[:menu_id])
end
end
if you want to show it on the menu, we need a new Item to display.
menus_controller.rb
def show
#menu = Menu.find(params[:id])
#item = #menu.items.new
end
Then we need to make use of the nested resource in the menus/show view. By passing in the array of the menu and the item, rails will generate the correct path.
menus/show.html.erb
<%= form_for [#menu,#item] do |f| %>
Are you posting to "/post" or "post/[:id]"?
#menu = Menu.find(params[:id])
Will not find anything if you do not pass it an id. The id can come from the URL params, but this means you should be sending your request to "/post/[:menuid].
You can use the gem https://github.com/charliesome/better_errors to debug your program at the controller level and execute rails console there. just put in "fail" in your controller and then you will have a command line interface where you can inspect your params value and ensure it has the id included and play with the console there.
I have now solved it. Thanks to better_errors I was able to better understand what was going on.
I implemented the show action in menus_controller.rb from aaron.
def show
#item = Item.new
#menu = Menu.includes(:items).find(params[:id])
end
I needed a way to pass the menu id into the menu_id field, so I added a hidden field in the table or show.html.erb passing the current #menu.id into the :menu_id field.
<%= f.hidden_field :menu_id, :value => #menu.id %>
I have read online that passing values through hidden fields is not a great idea though.
Then in the create action of the Items Controller I was able to make a new record as usual redirecting to the existing show view using the menu id from the item object.
def create
#item = Item.new(item_params)
if #item.save
redirect_to menu_path(#item.menu_id)
else
redirect_to menu_path(#menu.menu_id)
end
end
It feels like a bit of a hack so open to suggestions on how to improve.

undefined method `destroy' for nil:NilClass

My app successfully creates rows for a table every time a user submits some stats. Now I want to provide a delete button to be able to delete some rows.
When I click the delete button I get the error:
undefined method `destroy' for nil:NilClass
Seems like either rails doesn't understand the destroy method in this case for whatever reason, or rails doesn't see anything to destroy, hence the nil:NilClass.
My first question is in identifying which is the case, if either of those.
My second question is fixing it :D
Here's my show.html:
<% provide(:title, "Log" ) %>
<% provide(:heading, "Your Progress Log") %>
<div class="row">
<div class="span8">
<% if #user.status_update.any? %>
<h3>Status Updates (<%= #user.status_update.count %>)</h3>
<table>
<thead>
<tr>
<th>Entry Date</th>
<th>Weight</th>
<th>BF %</th>
<th>LBM</th>
<th>Fat</th>
<th>Weight Change</th>
<th>BF % Change</th>
<th>Fat Change</th>
</tr>
</thead>
<tbody class = "status updates">
<%= render #status_updates %>
</tbody>
<% end %>
</div>
</div>
The "<%= render #status_updates %>" calls the _status_update partial.
<tr>
.
.
.
.
<% if current_user==(status_update.user) %>
<td>
<%= link_to "delete", status_update, method: :delete %>
</td>
<% end %>
</tr>
And then finally, here is the StatusUpdateController
def destroy
#status_update.destroy
redirect_to root_url
end
Here are the parameters on the error page:
{"_method"=>"delete",
"authenticity_token"=>"w9eYIUEd2taghvzlo7p3uw0vdOZIVsZ1zYIBxgfBymw=",
"id"=>"106"}
As you are not showing any code (filters or so) that could assign #status_update, I assume there is none. So, the instance variable #status_update in your in your destroy method is not set. You need to query the class with the object's ID.
Rewrite your destroy method as follows:
def destroy
#status_update = StatusUpdate.find(params[:id])
if #status_update.present?
#status_update.destroy
end
redirect_to root_url
end
Note: Replace the classname StatusUpdate with your actual class name.

According to a lynda.com screencast, rails is supposed to be generating a button using the form_for (view) method, but it's not

EDIT: The first question is my premise itself. Is rails/html SUPPOSED to generate a "Create Subject" button without me explicitly asking it to?
So here is the controller that is working with the view
class SubjectsController < ApplicationController
def index
list
render('list')
end
def list
#subjects = Subject.order("subjects.position ASC")
end
def show
#subject = Subject.find(params[:id])
end
def new
#subject = Subject.new(:name => 'default')
end
def create
#instantiate a new object using form params
#subject = Subject.new(params[:subject])
#save the subject
if #subject.save
#if save succeeds redirect to list action
else
#if save fails, redisplay form
render('new')
end
end
end
And here is the misbehaving view (html.erb) file which isn't generating my button
<%= link_to("<< Back to List", {:action => 'list'}, :class => 'back-link') %>
<div class="subject new">
<h2>Create Subject</h2>
<%= form_for(:subject, :url => {:action => 'create'}) do |f| %>
<table summary="Subject form fields">
<tr>
<th>Name</th>
<td><%= f.text_field(:name) %></td>
</tr>
<tr>
<th>Position</th>
<td><%= f.text_field(:position) %></td>
</tr>
<tr>
<th>Visible</th>
<td><%= f.text_field(:visible) %></td>
</tr>
</table>
<% end %>
</div>
Currently, the output on the browser is:
'<< Back to List' (link)
<h2>Create Subject</h2>
Name [blank-form]
Position [blank-form]
Visible [blank-form]
[missing button location]
There is supposed to (according to lynda.com) be a button which says "Create Subject" in the missing button location, but it's not there.
Nothing in your code is supposed to generate a button.
You'll need to add:
<%= f.submit 'Create Subject' %>
inside the form. Maybe between </table> and <% end %>

Resources