I have a review model that I would like to lock out the edit and destroy button after 30 minutes and if only the correct user. Right now I just have an if statement around the button but you can still get to it by putting in the full URL. Where/ how would I go about doing this? I am new to Ruby on Rails and any help is useful. Thanks in advance!
Edit: All I have is below in the index but the problem is that I can still get to it through the URL and I don't know how to make those inaccessible after that.
<% if signed_in? %>
<% if current_user.id = review.user_id %>
<% if !review.has_time_passed? %>
<td><%= link_to 'Edit', edit_property_review_path(review.property, review) %></td>
<% if !review.comments.any? %>
<td><%= link_to 'Destroy', [review.property, review], :confirm => 'Are you sure?', :method => :delete %></td>
<% end %>
<% end %>
<% end %>
<% end %>
My has_time_passed method:
def has_time_passed?
created_at < 30.minutes.ago
end
There's at least 2 pieces to what I think you're describing:
You need to ensure the view template hides any edit and destroy links after 30 minutes.
You need to add logic to the relevant controller actions (edit and destroy) to ensure they'll refuse to make any changes after 30 minutes.
As far as the view logic goes, it sounds like you're pretty close and this shouldn't be too difficult. One if statement phrasing that comes to mind (similar to what you pasted above, but a tiny bit simpler):
if review.created_at <= 30.minutes.ago
Then, in the controller, you'll also want to ensure the action only makes changes within the time limit. So for example you might use the same if statement:
def edit
if review.created_at <= 30.minutes.ago
redirect_to some_other_path, alert: "Sorry bro, this review is too old to be edited."
else
# do stuff
end
end
That's just a very rough-draft example. Once you have everything working, then extract the logic into a method on the model (to reduce redundancy) and so forth.
Good luck!
My recommendation would be to introduce an authorization framework like Pundit or Cancan.
That way you separate the logic that interacts with the model from the controller, for which it's a good idea to keep it as minimalistic as possible.
Both Pundit and CanCan have great tutorials that show how to achieve similar scenarios to what you are trying to achieve.
In Pundit, for example, your policy would like somewhat like this:
class MyModelPolicy
attr_reader :user, :my_model
def initialize(user, model)
#user = user
#my_model = model
end
def destroy?
user == my_model.user && my_model.created_at < 30.minutes.ago
end
end
I think you want something at the controller level.
# app/controllers/reviews_controller.rb
class ReviewsController
before_action :validate_change, only: [:edit, :update, :destroy]
def edit
# edit stuff
end
def destroy
# destroy stuff
end
private
def validate_change
if #review.created_at < 30.minutes.ago
redirect_to request.env['HTTP_REFERER']
end
end
end
Related
Hello I have a lists of invoices that belong to a business and also the business belongs to a user, I am trying to have a button (link to) on a table in which all the invoices are listed for the user to be able to update the status of the invoice.
Pretty much if the user hits the link it will change from paid: true to paid: false and viseversa.
here are the routes:
resources :businesses do
resources :invoices
end
Here is the section of the table in which the link is:
<% if invoice.paid %>
<td><%= link_to "Mark as Not Paid", business_invoice_path(current_user, invoice), method: 'put', data: {paid: false} %></td>
<% else %>
<td><%= link_to "Mark as Paid", business_invoice_path(current_user, invoice), method: 'put', data: {paid: true}%></td>
<% end %>
Note: The paid column is a boolean on the db
Since, the paid column is present on Invoice, it is much better if you handle it at the controller or model level instead of getting the value from the form.
Remove if else conditions and combine it as below:
<%
invoice_text = invoice.paid ? 'Mark as Not Paid' : 'Mark as Paid'
%>
<td><%= link_to invoice_text, business_invoice_path(invoice), method: :put %></td>
In the Business::InvoicesController you can write the logic in update like this:
Business::InvoicesController < ApplicationController
before_action :authenticate_user!
before_action :set_invoice
def update
# TODO: find the business using the invoice
# have a check in place to authorize the
# transaction (if invoice belongs a business which
# belongs the current_user or not, if not then raise Unauthorized error)
# if business does belongs to the current_user then proceed to next step
# invert the value of paid column based on existing value
#invoice.update(paid: !#invoice.paid)
end
private
def set_invoice
#invoice = Invoice.find(params[:id])
end
end
With logic above, you can forget about maintain/finding the value of paid column since you have an option to revert the value of paid to true and back to false. Also, that I assumed you are using Devise for authentication.
I'm making a system for study purposes.
This system controls the hospital shifts for the user. An user should only see his shifts.
I'm using CanCanCan for the authorization control.
So for the index page the controller action I have:
class UsersController < ApplicationController
def index
#users = User.all
end
end
In the index.html.erb
<% #shifts.each do |shift| %>
<% if can? :read, shift %>
<tr>
<td><%= shift.date %></td>
</tr>
<% end %>
<% end %>
In the ability I have:
class Ability
include CanCan::Ability
def initialize(user)
if user
can :manage, Shift, user_id: user.id
end
end
end
I want to show a message to the user if he doesn't have any shifts.
But using the can method in index html page because if the list is not empty but there is no shift the current user can see I can't use an empty method direct in the #shift.
Is it possible to filter the list in the index action in user controller?
You're asking a few different questions here, and it's hard to answer them individually, so I'm going to take a more broad approach in hopes of answering some of the underlying questions I believe you're having.
First thing's first: Cancancan does not do anything that relates to querying your database. Cancancan leaves that up to you.
I'm not seeing where #shifts is defined, so I'm going to assume it's in your ShiftsController — whereas the index action is supposed to show a given user's shifts.
A given user is usually the current_user in Rails applications — this authentication is done by something such as Devise. If your associations are setup correctly, you're able to do #shifts = current_user.shifts
But in the case that you want to, say, filter the shifts by user — such as having a RESTful route of /users/:id/shifts — you would have routes that look like this:
resources :users do
member do
get :shifts => 'users#shifts'
end
end
which would map to your UsersController with a method shifts.
Inside that method you'd have an :id param with the value that you'd expect from your show action.
#shifts = Shift.where(:user_id => params[:id])
Now, if you wanted to filter shifts on your ShiftsController#index method, you could have something like this:
#shifts = Shift.where(:user_id => params[:user_id])
and your URL would look like, presumably, /shifts?user_id=1
Now, if a user doesn't have any shifts, #shifts will return an empty array — indicative of no rows that match your database query. Therefore, you can do some simple logic in your view,
<% if #shifts.empty? %> No shifts. <% else %> ... things to list shifts <% end %>
I'm probably trying too hard for my first website but i wanted to make a dropdown on a (bootstrap) navbar to be flexible, and show the names of the saved work categories.
This is what i've tried to do in the application.html.erb file :
<ul class="dropdown-menu">
<% #workcategory.each do |workcategory| %>
<li><%= workcategory.name%></li>
<% end %>
Failed with error undefined methodeach' for nil:NilClasson the<% #workcategory.each do |workcategory| %>` line.
This is the workcategories controller :
class WorkcategoriesController < ApplicationController
before_action :find_workcategory, only: [:edit, :update, :destroy]
def index
#workcategories = Workcategory.all.order("created_at DESC")
end
def new
#workcategory = Workcategory.new
end
def create
#workcategory = Workcategory.new(post_params)
if #workcategory.save
flash[:notice] = "Workcategory created"
redirect_to(:action=>'index', :workcategory_id => #workcategory.id)
else
#workcategories = Workcategories.order()
render('new')
end
end
def edit
end
def update
end
def destroy
#workcategory.destroy
redirect_to workcategory_path
end
private
def find_workcategory
#workcategory=Workcategory.find(params[:id])
end
def post_params
params.require(:workcategory).permit(:name)
end
end
Any tips and help are welcome, even non-related to the initial question :) Thank you
If you want it in all ur actions, it is wise to put it in your application_controller.rb.
before_filter :set_work_categories
def set_work_categoriers
#w_categories = Workcategory.all.order("created_at DESC")
end
This should work fine.
Also, a tip.
You can use default_scope {order(created_at: :desc)} in your model WorkCategory.rb
Then you can use this like,
def set_work_categoriers
#w_categories = Workcategory.all
end
I would recommend changing the variable name to #w_categories or else it will conflict with your #work_categories name in index action.
In your application.html.erb file, change
<% unless #w_categories.nil? %>
<ul class="dropdown-menu">
<% #w_categories.each do |workcategory| %>
<li><%= workcategory.name%></li>
<% end %>
</ul>
<%end>
I guess this should do the trick
If we talk about index action, then you just forgot to use appropriate variable:
<ul class="dropdown-menu">
<% #workcategories.each do |workcategory| %>
<li><%= workcategory.name%></li>
<% end %>
Update
If you want to have this in all actions, then initialize #workcategories in before_action:
# your_controller.rb
before_action :initialize_work_categories
def initialize_work_categories
#workcategories = Workcategory.all.order("created_at DESC")
end
Layouts
application.html.erb is a layout, meaning that it will be present regardless of whether you're using the Workcategories controller or not.
If you want to load a variable into the layout, irrespective of which controller is being invoked, you'll need to make sure the #workcategory variable is present.
To do this, you would generally put the #workcategory declaration into the ApplicationController (which most other controllers inherit from):
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :set_categories
private
def set_categories
#workcategory = ....
end
end
This is the standard way to populate layout-side variables. It's not efficient and only works if your controllers inherit from the Application controller.
Some further pointers would include the following:
1. Object orientation
Ruby, and by virtue, Rails, is object orientated.
This means that everything you do should revolve around objects. This is why Rails has many helpers that seem to work "magically".
They're not magic at all - they just take the objects you pass them, and build pre-baked HTML to give you specific functionality.
A good example are the routes:
#config/routes.rb
resources :controller
The reason this is important is that when you call actions / variables, you need to think of them as objects. This is difficult to do for newbies, but if you can get your head around it, it helps your coding massively.
--
2. Controllers
Following on from the above, you have to remember that your controller is really just a way to manipulate objects.
Thus, if you're calling #workcategory, you need to appreciate where the object is going to come from and how it's going to be populated.
You can make a helper method work_categories and use that. Either define it directly in application_helper - or if you don't want to use helpers you can put this code in your ApplicationController
class ApplicationController < ActionController::Base
def work_categories
Workcategory.all.order("created_at DESC")
end
helper_method :work_categories
end
Then in your view:
<ul class="dropdown-menu">
<% work_categories.each do |workcategory| %>
<li><%= workcategory.name%></li>
<% end %>
</ul>
My admin accounts are user accounts with a simple boolean set to true. Works fine for now, I can control functionality with if statements in the views, example:
<td><% if current_user.admin? || current_user == user %>
<%= link_to "Edit", edit_user_path(user) %>
<% end %></td>
Some resources are off limits to anonymous users, and they get redirected to the login page if they try and select those links. But other resources (like a list of all the articles on the site), I want both those with a session and those without to see. The problem of course is that a user with no session will throw an error, because there is no current_user if you don't have a session. So I decided to divide up the world into the 2 parts. When you hit the index.html.erb for 'articles', this is all that in there:
<% if current_user == nil %>
<%= render "anonindex" %>
<% else %>
<%= render "authindex" %>
<% end %>
My question is, am I making a long term design mistake by doing this? Am I eventually going to need to implement a roles based system, or is it feasible to differentiate user privileges based on boolean operators, and keep the users with no session in a completely separate sandbox? Its working great thus far, but I worry I'm going down a path that will require a total rebuild later.
You don't actually have to check this thing in views. You can check this thing in Controller, and can take the appropriate out there:
class YourController < ApplicationController
before_action :check_user_logged_in, only: [:index, :your_desired_method]
end
And then in check_user_logged_in method, you can see if a user is logged in, send him to the desired place, otherwise redirect him to the log in page.
def check_user_logged_in
redirect_to log_in_path unless current_user
end
I have a rails app that is working fine using acts_as_votable. The like button upvotes the post count, and then switches to an un-like button and this down votes the post count.
My issue is, that since I started using the Public Activity gem, I can't find a way to remove likes from the feed. I have used the following loop in the activities index view:
<% #activities.each do |activity| %>
<p>
<% if activity.trackable %>
<%= link_to activity.owner.name, activity.owner %>
<%= render_activity activity %>
<% end %>
</p>
<% end %>
When I delete a comment, the entire line in the activity feed of 'FOO added a comment on BAR' disappears. However, because the acts as votable gem actually creates a downvote rather than destroying the upvote, the line 'FOO liked BAR' still appears and would be subsequently followed by 'FOO unliked BAR'.
Does anybody know how I can locate the upvote by the current_user on a particular post and then destroy it?
Below is my controller code for like and unlike as it stands:
def like
#ink.create_activity :like, owner: current_user
#ink.upvote_by current_user
redirect_to :back
end
def unlike
#ink.downvote_by current_user
redirect_to :back
end
Thanks
I know this have been answered but the ideal and easiest answer would be using the official gem method which is unvote_by it works for both upvotes and downvotes.
It looks like what you want to do is remove the model's like notification when it is "unliked" - is that correct? All you have to do is find the relevant activity and destroy it. Because Activities are models just like any other, you can do this with destroy and/or destroy_all.
I'm not quite sure what your public_activity model looks like, so instead of giving a specific example I'll link you to this post on the mailing list for the gem, which shows examples of deleting public activity records.
You may also find it useful to delete the record by its trackable_id - for example:
#activity = PublicActivity::Activity.find_by(trackable_id: (params[:id]), trackable_type: controller_path.classify)
#activity.destroy
There's more information about how that works in this SO answer.
For anyone that stumbles across this in future, thanks to Element119 I was eventually able to pinpoint the current users likes on a particular post with a few variables and arel searches. I then destroyed the pinpointed likes.
def like
#post.create_activity :like,
owner: current_user,
recipient: #post.user
#post.upvote_by current_user
redirect_to :back
end
def unlike
#post.downvote_by current_user
#currentUserLikes = PublicActivity::Activity.where(trackable_id: #post.id, owner_id: current_user.id, key: "post.like")
#currentUserLikes.destroy_all
redirect_to :back
end