CRUD Ownership of Items/Microposts in Rails App - ruby-on-rails

I have a simple rails app where users can create 'items', but on the master index page that lists all the items, there are 'Show, Edit, and Delete' links next to each 'item'. I understand that this is due to the fact that I used scaffolding to accomplish the items, but I'd like to make sure that people can only edit the ones that they created. This logic is a little above my head at the moment, as, like I've said before, am totally new to rails.
User Controller:
class UsersController < ApplicationController
def show
#user = User.find_by_username(params[:id])
end
def index
#user = User.find(:all)
end
end
Master Item View:
<div class="well">
<h1>All Items</h1>
<table>
<tr>
<th>Title</th>
<th>Details</th>
<th>Inquire</th>
<th></th>
<th></th>
<th></th>
</tr>
<% #items.each do |item| %>
<tr>
<td><%= link_to item.title, item_path(item) %></td>
<td><%= item.content %></td>
<td><%= mail_to item.email, "Inquire", :cc => "michaelomchenry#gmail.com",
:subject => "OverFlow Inquiry Regarding " + item.title %></td>
<td><%= link_to 'Show', item %></td>
<td><%= link_to 'Edit', edit_item_path(item) %></td>
<td><%= link_to 'Destroy', item, confirm: 'Are you sure?', method: :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Item', new_item_path %>
</div>
Item Model:
class Item < ActiveRecord::Base
attr_accessible :content, :user_id, :title
validates :content, :length => { :maximum => 140 }
belongs_to :user
delegate :email, to: :user
end

There is a lot of stuff there. First of all, to be able to filter the items (or the actions) based on the user, you need to know who is logged at the time, and so enable users to login. This could be done for example using Devise, an authorization gem for Rails. Devise is largely used and well documented.
Once Devise is setup, you can check whether the user is logged (for example using a before_filter), and Devise will create a "current_user" variable for you to use (this is shown in Devise tutorial). You can then use it to filter your item lists with something like :
editable_items = current_user.items
And then use the editable_items on your view. I would advise you to go read the Devise tutorial as what you are doing is a quite common and well documented task.

I would comment if I could, but I feel that this must be said in reference to the answer posted by #momchenr (answering their own question) as a follow up to the chosen answer.
#momcher wrote:
I ended up doing this:
<% if item.email == current_user.email %>
and it worked... is that ok?
Probably not. But that depends on how your system is set up. If users can edit their email addresses and/or email addresses aren't forced to be unique, they may be able to gain editing access to another user's "items" by changing their email address temporarily or just signing up as a new user with a known user's email address.
Even if you never display email addresses of users in your application, there is an inherent vulnerability when you leave that much of the authentication process in a user-provided field.
Not knowing exactly how things are set up in Devise—only based on the info you have provided—I'd try the following:
These two might be are a touch slower depending on the state of ActiveRecord when called
<% if item.user == current_user %>
<% if item.user.id == current_user.id %>
This one ought to be a touch faster since you are not getting the user object from the item object (you are just pulling directly from the user_id method of the user object)
<% if item.user_id == current_user.id %>
No matter if I am right or wrong in my guesses at speed, this is generally a better solution than the one you said works for you. Since the user's ID is never under their control directly—unless there are major holes in your code—they cannot easily pose as other users.

I ended up doing this:
<% if item.email == current_user.email %>
and it worked... is that ok?

Related

Ruby on Rails proper way to add a favorite button for front end

So far I have two tables User and Company. Users are able to favorite many companies. I established all the back end code to add favorites (a join table with many to many relationship) I also have a general setup to add favorites.
I need some help with the front end side though.
So far what I have is a search page where users can query a company ticker to get companies. I display it like the following
<% if #results %>
<h4>Search results for <%= #symbol %></h4>
<table>
<tr>
<th>ID</th>
<th>Symbol</th>
<th>Name</th>
</tr>
<% #results.each do |res| %>
<tr>
<td><%= res.id %></td>
<td><%= res.symbol %></td>
<td><%= res.name %></td>
<td><%= link_to "Favorite", create_favorite_path(current_user.id, res.id), method: :post, :remote => true %></td>
</tr>
<% end %>
</table>
<% end %>
Right now I have a "favorite button" implemented with
<td><%= link_to "Favorite", create_favorite_path(current_user.id, res.id), method: :post, :remote => true %></td>
It seems messy and I dont think this is right. I also want to add a few more features
I want to know if the post method was successful or not
Based on the post, I want to be able to change the button text to something like "saved"
later change the link to have a star button icon
I would love suggestions to improve this and do it properly
I prefer using a front-end framework to handle things like this. They allow the components to be much more dynamically interactive.
re: I want to know if the post method was successful or not
in the absence of a front-end framework you can catch failed save in the favorites_controller and add a flash[:error] to the page.
flash[:errors] = #favorite.errors.full_message unless #favorite.save!
re: Based on the post, I want to be able to change the button text to something like "saved"
You can use jQuery to change the button CSS on click of the favorite button.
<% if #results.favorited? %>
# render button with favorited class (with star)
<% else %>
# render button without favorited class (without star)
<% end %>

Create method does not add any records to my index list

I'm new to rails and I'm trying to pass crud methods.
I made a Leads model, controller, view and routes.
When I submit the form of my /leads/new page if i write post 'leads/index'
in my route.rb file, my browser directs me to the index page where my new record does not show up.
If I skip the code above in route I get routing errors.
Controller :
def new
#leads = Leads.new
end
def create
#leads = Leads.new(params[:id])
if #leads.save
redirect_to(:action => index)
else
render(new)
end
end
Route:
resources :leads
View Index:
<h1>Leads#index</h1>
<%= link_to 'Add new Leads', new_lead_path %>
<% #leads.each do |lead| %>
<tr>
<td><%= lead.name %></td>
<td><%= lead.familyname %></td>
<td><%= lead.mobile %></td>
<td><%= lead.email %></td>
<td><%= link_to 'show' , lead %></td>
</tr>
<% end %>
View New:
<h1>Leads#new</h1>
<%= render 'form' %>
First -- Sontya is correct, your Lead model should be singular. This will help fix the routing issues.
Secondly, you are passing params[:id] in to Leads.new, which will make a lead with an explicitly passed id, rather than letting ActiveRecord handle that. You want to pass (I am guessing, as you dont have your form partial code up) params[:lead] instead (or whatever the form calls it).
Also, there are minor issues, like calling your single created Lead #leads (should be #lead) -- this shouldnt cause any issues, but it is better convention/better for readability.
Rails says that model name should be singular and controller names should be plural. If you follow it is better for your understanding and all others understanding.

Nested forms in rails for existing objects

In my app, I have a page where I want admin users to be able to update a particular characteristic of my "Package" model, which belongs to both the "Order" model and the "Item" model. It's a little complicated, but I basically want to present in a table all of the Packages belonging to a given Item, ordered in a particular way (that's what my packages_for_log method below does), with two blanks for updating the weight of the item. All the updates should ideally be submitted at once, with a single submit button at the bottom of the page. I've attempted a whole bunch of solutions, and my current one is below, but gives this error when I visit the page in my server:
undefined method `actual_lbs' for #<ActionView::Helpers::FormBuilder:0x007ff67df6c9c8>
The error's confusing to me, cause I was hoping that I was calling that method on the package instance, not a helper. Bit confused. At any rate, my code is below. The relevant section of the view:
<% form_for(#item) do |a| %>
<% #item.packages_for_log.each do |p| %>
<%= a.fields_for p do |i| %>
<tr>
<td><%= p.order.name %></td>
<td><%= p.order.processed_notes %></td>
<% if p.order.user %>
<td><%= "#{p.order.user.name.first(3).upcase}-#{p.id}" %></td>
<% else %>
<td><%= p.order.id %></td>
<% end %>
<td>
<%= i.text_field :actual_lbs %>
</td>
<td>
<%= i.text_field :actual_oz %>
</td>
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16) %>
</tr>
<% end %>
<% end %>
<% end %>
Relevant section of the package.rb model file:
attr_accessible :order_id, :price, :true_weight, :actual_lbs, :actual_oz
attr_accessor :actual_lbs, :actual_oz # These two are virtual attributes for the above calc
And I added resources :packages to my routes file.
Any idea what I'm doing wrong? It's important to me that I loop through to create a table based on "p" and then edit that same "p" object. Just not sure how to do it. Pretty new to Rails.
I think your problem is this line:
<%= i.hidden_field :true_weight, value: (i.actual_lbs + i.actual_oz/16)
You need to put p.actual_lbs and p.actual_oz
EDIT: By the way, you probably need to move the true weight calculation to your controller action (CREATE action). I don't think :true_weight will get passed as you intended it to, using the above method.

My controller is -persistently- sending wrong params[:id] in Rails?!

I'm new to Ruby on Rails & to web programming.
In my application I have two models; Directorate which has_many :users, and User which belongs_to :directorate.
When creating a new user, I use <%= f.collection_select(:directorate_id,Directorate.all, :id, :name) %> in the new.html.erb form to assign the new user to specific directorate. However, I want to build a user-friendly interface for the dba that lists all directorates; and listing all users beside each directorate, with a link to assign any user to a specific directorate.
What I did is the following:
In Directorate model, I defined the following function:
def assign_user!(user)
user.update_attributes(directorate_id: #directorate)
end
and in the directorates controller, I defined the following action:
def assign_user
#directorate = params[:directorate]
assign_user! params[:user]
redirect_to directorates_url
end
Now, directorates/index.html.erb contains the following:
<h1>Listing directorates</h1>
<table>
<tr>
<th>Name</th>
<th>Info</th>
</tr>
<% #directorates.each do |directorate| %>
<tr>
<td><%= directorate.name %></td>
<td><%= directorate.info %></td>
<td><%= link_to 'Show', directorate %></td>
<td><%= link_to 'Edit', edit_directorate_path(directorate) %></td>
<td><%= link_to 'Destroy', directorate, confirm: 'Are you sure?', method: :delete %></td>
<%= #directorate = directorate%>
<%= render 'users_form' %>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Directorate', new_directorate_path %>
and, -users_form.html.erb contains the following form (which is supposed to list all users beside each directorate, with a link to assign any user to a certain directorate):
<h1>Listing Users</h1>
<table>
<tr>
<th>User Name</th>
</tr>
<% #users.each do |user| %>
<tr>
<td><%= user.username %></td>
<td><%= link_to 'Assign to Current Directorate', {controller: 'directorates', action: 'assign_user', directorate: #directorate, user: user}, :method => :put %></td>
</tr>
<% end %>
</table>
<br />
Here is the problem, when listing directorates & click on the 'Assign to Current Directorate' I receive the following error:
http://127.0.0.1:3000/directorates/assign_user?directorate=4&user=5
ActiveRecord::RecordNotFound in DirectoratesController#update
Couldn't find Directorate with id=assign_user
Rails.root: /home/ehab/sites/IAMS
Application Trace | Framework Trace | Full Trace
app/controllers/directorates_controller.rb:61:in `update'
Request
Parameters:
{"_method"=>"put",
"authenticity_token"=>"L5tz3hv2IW0meE79qUq0/tjfGKwDlpC23hOeAWtmTvk=",
"directorate"=>"4",
"user"=>"5",
"id"=>"assign_user"}
It's clear that the params is submitting "id"=>"assign_user" which I don't want, what i want is "id"=>"directorate.id" (4 in the above example). What shall I do to fix this issue?!
first of all your routes should say that assign_user is a member method on a certain directorate object:
resources :directorates do
member do
put :assign_user
end
end
second you say you define assign_user! in Directorate model and assign_user in DirectoratesController but both methods imply that they share same object state like instance variable #directorate which is not true
your controller method assign_user should look vaguely like
def assign_user
#directorate = Directorate.find params[:id]
#user = User.find params[:user_id]
#directorate.assign_user! #user
end
and model method should look like
def assign_user!(user)
user.update_attributes(directorate_id: self.id)
end
and even that i would switch around to instead of telling Directorate to change user's attributes you would tell User to assign itself to whatever controller wants.
and the final bit is your link that assigns user to directorate:
link_to 'Assign to Current Directorate',
assign_user_directorates_path(#directorate, :user_id => user)
0 lines of code above were tested for even syntactical correctness, DO NOT copy-paste, read and understand

Rails 3 accessing has_many through join model in another controller

I'm doing an online judge application, so I have a User model, a Problem model and a Solution model to make the many to many relation. In that Solution model I have an extra column called "state" where I plan to store the state of a problem for a certain user: solved, wrong anwser, not solved.
I'm trying to modify the index action in my problems controller to render the state of the problem in the problem list (so a user can see if he has solved a problem or not, like I said before). Nevertheless I'm having an "uninitialized constant Admin::ProblemsController::Solution" error when I access the view.
I'm really new to RoR and my experience so far has been really harsh, so I'll appreciate any leads. Here is the code in the controller and the view:
problems_controller.rb
def index
#problems = Problem.all
if current_user
#solutions = Solution.includes(:problem).where(:user_id => current_user.id)
end
respond_to do |format|
format.html # index.html.erb
format.json { render json: #problems }
end
end
views/problems/index.html.erb
<% #problems.each do |problem| %>
<tr>
<td><%= problem.name %></td>
<td><%= problem.code %></td>
<td><%= problem.description %></td>
<% if current_user %>
<%= for solution in #solutions do %>
<% if solution %>
<td><%= solution.state%></td>
<% else %>
<td>Not Solved</td>
<% end %>
<% end %>
<% end %>
<td><%= link_to 'Show', problem %></td>
<% if current_user && current_user.is_admin? %>
<td><%= link_to 'Edit', edit_problem_path(problem) %></td>
<td><%= link_to 'Delete', problem, method: :delete, data: { confirm: 'Are you sure?' } %></td>
<% end %>
</tr>
<% end %>
I'm not sure if that's the best way I should be accessing the Solutions table or if I should be doing that in another controller (in the users controllers? in a solutions controller file perhaps?).
I want to be clear of how to use that "Solutions" join table. I had a has_and_belongs_to_many before and changed it because of the extra column. I've read a lot about many to many relationships, but I can't understand it for this case =(
Just need to use:
problem.solution.state
Unless a problem may have many solutions, then it would need to be something like:
problem.solutions.first.state
However this will just give the state of the first, so I'd define a method in Problem which calculates a status (eg. If any of the solutions solve it then problem is solved)
For 1 problem, many solutions for a given user.
In Solution.rb
scope :for_user, lambda {|user_id| :conditions => {:user_id => user_id}}
Then we can call:
problem.solutions.for_user(current_user.id).first.state
It might look a bit long but it's highly flexible.

Resources