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.
Related
I am trying to display only the rows that belong to certain states in my application. I can do it the long way in Javascript, but I would prefer to better understand Rails and queries in the controller. I want to take the users to another page and then show them only that the companies in that state. It would be great to not have to link them to another page. Does anyone know how to do this?
Here is what I have in my controller
def vendors
#vendors = Collective.where(sort: 'Vendor').all
#vendors = #vendors.where(params[:state])
end
My route
get '/vendors/:state', to: 'collectives#vendors'
Then I use the stereotypical method to print a table in a html.erb file.
<% #vendors.each do |company| %>
<tr>
<td><%= company.name %></td>
<td><%= company.state %></td>
etc...
Should your controller code change the where as follows:
def vendors
#vendors = Collective.where(sort: 'Vendor').all
#vendors = #vendors.where(state: params[:state])
end
or better:
def vendors
#vendors = Collective.where(sort: 'Vendor', state: params[:state])
end
Using sessions instead of url params.
This is more or less what you can do, sorry if it is not completly working for your case, just to give an idea.
# view collectives/index (or whatever you have)
<%= form_tag (controller: :collectives, action: :set_status_filter, method: :post) do %>
<%= select_tag(:session_status_filter, options_for_select(#your_list_of_options_for_the_filter)) %>
<%= submit_tag "Set filter" %>
<% end %>
# collectives controller
def index # or whatever, this is the page containing the form and the list to show
#vendors = Collective.where(sort: 'Vendor').all
if session[:session_status_filter] == # etcetera
then #vendors = #vendors.where(state: session[:session_status_filter]) # for example
else # another option just in case, etcetera
end
end
def set_status_filter # this action is called by the form
session[:session_status_filter] = params[:session_status_filter]
respond_to do |format|
format.html { redirect_to *** the view where the form is placed ***, notice: 'the filter is set to: ....' + session[:session_status_filter] } # after the session variable is set the redirects goes to index which uses the session to filter records
end
end
params[:session_status_filter] is passed by the form to collectives#set_status_filter. The value is used to set the session variables. After that the action collectives#set_status_filter redirects to the index, or whatever page you placed the form and the list to show.
My app uses devise and devise_invitable to handle invitations. On my new invitations view I have a table that lists all users that have been invited. I want to add a column to delete each listed user. If I understand correctly, the route expects a format that should probably be something like remove_user_invitation_path(something) and also that it appears to be an invitation_token (an attribute of the User model). I just don't know how to insert it into the URL as a format. I'm also not sure if this will delete the user itself or the invitation (* I placed the) given that a user can accept or ignore an invitation, I worry that it will only work for users that have a pending invitation. Any input is appreciated!
$ rails routes
remove_user_invitation GET /users/invitation/remove(.:format) users/invitations#destroy
# users/invitations/new
<% #invited_users.each do |invited| %>
<tr>
<td><%= invited.email %></td>
<td><%= link_to "Delete", remove_user_invitation_path, method: :delete, data: { confirm: "Are you sure you want to delete this user?" } %></td>
<tr>
<% end %>
# GET /resource/invitation/remove?invitation_token=abcdef
def destroy
resource.destroy
set_flash_message :notice, :invitation_removed if is_flashing_format?
redirect_to after_sign_out_path_for(resource_name)
end
# What my variable contains
#invited_users = User.where.not(invitation_sent_at: nil)
user.rb
def has_delete_role? name
roles.each do |n|
return true if n == name
end
end
ability.rb
if user.has_delete_role? :business_delete
can :destroy, Business
end
index.html.erb
<% if can? :destroy, #business %>
<%= link_to 'delete', business_path(#business.id), method: :delete%>
<% end %>
This piece of code allow user who has the authority to access delete button. Here if a user has authority, he can access delete buttons of all objects. EX: Business has 10 objects id = 1 to id = 10, user can access all of 10 delete buttons if he has the authority
But now I want to set the authority base on object.
EX: Buisness also 1 to 10, user can only see button 2 and 5 because there is a field in user data table called auth_ids [], it stores [2,5]
How to achieve this?
You can use:
can :destroy, Business, id: user.auth_ids
You can set up a condition, something similar to the guide here: https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
can :destroy, Business, Business.where('id = ?', user.auth_ids)
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
I've been borrowing some code from an old ruby on rails 3 app of mine for a new rails 4 app. The code works on the old site, but on the new one it doesn't.
Here's my routes.rb:
scope ':username' do
resources :recipes
end
get "/:username" => "recipes#index"
Here's my controller index:
def index
#user = User.find_by_username params[:username]
#recipes = Recipe.all
end
and my view:
<% #recipes.each do |recipe| %>
<tr>
<td><%= recipe.name %></td>
<td><%= link_to recipe.name, recipe_path(username: #user.username, id: recipe.id) %></td>
<td><%= link_to 'Edit', edit_recipe_path(recipe) %></td>
<td><%= link_to 'Destroy', recipe, method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
but the error pops up:
undefined method `username' for nil:NilClass
but the current user username is set to test so its not nil, and the error shouldn't be popping up.
Thanks for all help!
When you have variables that could be potentially be nil, you should handle the case where they are nil, unless you explicitly wish for it to fail in these circumstances.
The dynamic find_by methods return nil when no records are found. Hence, as alfonso pointed out, you are getting a null value for #user.
In this particular instance, I would question how you are using the username param; if recipes are associated with recipes, then I would set up a has_many :recipes in my user model, and belongs_to :user in my recipe model.
Since the user is the 'parent' here, I would opt to create a recipes action in the UsersController. It seems more logical to me to put the recipes that belong to a user in the user's controller, and access the recipes as a collection from the user.
Alternatively, if you are trying to show recipes associated with a user, I would put the action in the RecipesController, and get the user that belongs to a recipe by using the #user method set up from the belongs_to relationship in the database.
In either case, you'll want to guarantee that the users and/or any recipes are defined before trying to render a page. You might want to display a 501 error or something similar if a user doesn't exist that's trying to be accessed, etc.
If you insist that there should always be a user for a recipe, then you should add that type of validation to the recipes model, so that adding a recipe without a user is disallowed:
validates :user, :presence => true
Sorry if I went a little off tangent.
get "/:username"
Here - :username is in position of id
#user = User.find_by_username params[:username]
Here you are trying to find it by params.
Link should look like this http://localhost:3000/user_name/user_name?username=user_name
to find some user, and it is obviously not what you want to achieve.
get "/:id" => "recipes#index"
#user = User.find(params[:id])