I'm not good at ruby but I have to make a project using rails and I encountered a problem. I'm making a forum application where a user can have multiple profiles and change between them. After many hours I thought that maybe it would be easier if I added a whole column to the Users table called current_profile_id where I'd store the current chosen profile by the user. I am using the PATCH method to insert a profile id into the table but upon doing that the transaction gets rolled back and I am left with nothing. I am a total newbie and kinda don't want to do this so my code is most likely terrible.
view:
<% #user.profiles.each do |profile| %>
<%= link_to content_tag(:div, profile.name), users_profiles_path(:current_profile_id => profile.id), method: :patch %>
<% end %>
<div>
<%= current_user.current_profile_id %>
</div>
user controller:
def setProfile
#user = User.find_by(id: params[:id])
#user.current_profile_id = params[:current_profile_id]
#user.save
end
route:
patch '/users/:id/profiles', to: 'users#setProfile'
screenshot from wsl teminal
I know the idea isn't really thought through but at this point I kind of have to roll with it.
The problem isn't with your patch request per se: it's that you don't have a view template set up for the controller to forward to. Try creating view template to go with your setProfile action, or redirect the request to another location from that action.
I take an example from Rails Tutorial about "Follow a user".
<%= form_for(current_user.relationships.build(followed_id: #user.id)) do |f| %>
<div><%= f.hidden_field :followed_id %></div>
<%= f.submit "Follow", class: "btn btn-large btn-primary" %>
<% end %>
This line: f.hidden_field :followed_id
is generated into something like this in the browser:
<input type="hidden" value="13">
'13' indicates the ID of a user who is going to be followed.
Perhaps not all of my website users are tech savvy, but some users may found a way to do some tricks, e.g by opening the Firebug and simply edit the html input tag and set the value with random number.
It is clearly fast and simple to make a user to follow another user through the following way in my controller:
def create
#user = User.find(params[:relationship][:followed_id])
current_user.follow!(#user)
end
Also, I need the attribute in my model to be accessible:
attr_accessible :followed_id
However, isn't it too dangerous to let this kind of data being manipulated easily by users? Is there any other solution instead of doing like the codes above?
Your solution is fine. If there are any cases where the user can tamper with data in a malicious way, you need to have some sort of server-side check. If the user may only follow certain other users, for example, then you need to check that they haven't tampered with that variable to follow a user that they aren't allowed to. It depends on the situation.
As a rule of thumb, data that comes from the user should not be considered trustworthy. Always assume that it may be tampered with. However, if they cannot tamper with it in a way that serves a malicious purpose (I.e. If users may follow any other user), then don't worry about it.
I'm trying to hide parts of my views depending on the User role.
So let's say I want only admins to be able to destroy Products. Besides the code in the controller for preventing regular users from destroying records, I would do the following in the view:
<% if current_user.admin? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
The previous code works, but it's prone to errors of omission, which may cause regular users to see links to actions they are not allowed to execute.
Also, if I decide later on that a new role (e.g. "moderator") can delete Products, I would have to find the views that display a delete link and add the logic allowing moderators to see it.
And if there are many models that can be deleted only by admin users (e.g. Promotion, User) maitenance of all the ifs would be pretty challenging.
Is there a better way of doing it? Maybe using helpers, or something similar? I'm looking for something maybe like this:
<%= destroy_link 'Delete', product %> # Only admins can see it
<%= edit_link 'Edit', promotion %> # Again, only admins see this link
<%= show_link 'Show', comment %> # Everyone sees this one
I found these two questions that are similar to mine, but none of them answered my question:
Show and hide based on user role in rails
Ruby on Rails (3) hiding parts of the view
I strongly recommend pundit.
It allows you to create "policies" for each model. For your Product model you might have a ProductPolicy that looks something like this
class ProductPolicy < ApplicationPolicy
def delete?
user.admin?
end
end
In your view you can do something like this
<% if policy(#post).delete? %>
<%= link_to 'Delete', product, method: :delete %>
<% end %>
If later on you want to add a moderator role, just modify the policy method
class ProductPolicy < ApplicationPolicy
def delete?
user.admin? || user.moderator?
end
end
So I kind of figured a way to move the IFs out of the view. First, I override the link_to helper in my application_helper.rb:
def link_to(text, path, options={})
super(text, path, options) unless options[:admin] and !current_user.admin?
end
Then on my views I use it as:
<%= link_to 'Edit Product', product, admin: true, ... %>
This prevents regular users from seeing admin links, but for other html tags with content inside, such as divs, tables etc., an if would still be needed.
CanCan is another gem that lets you define "Abilities" per user role.
In views you can use something like if can? :delete, #post to check if the
user may delete that specific post.
Using the CanCan and Role gems, what is still needed is a way to Check The Route and see if "current_user" has permissions to access that Route based on their role(s) - then show/hide based on that.
This saves the user clicking on things and getting told they cannot see it - or us having to write per-item "if" logic specifying what roles can see what list-items (which the customer will change periodically, as roles are changed/refined) around every single link in one's menu (consider a bootstrap menu with 50+ items nested in groups with html formatting, etc), which is insane.
If we must put if-logic around each menu-item, let's use the exact same logic for every item by checking the role/permissions we already defined in the Ability file.
But in our menu-list, we have route-helpers - not "controller/method" info, so how to test the user's ability to hit the controller-action specified for the "path" in each link?
To get the controller and method (action) of a path (my examples use the 'users_path' route-helper) ...
Rails.application.routes.recognize_path(app.users_path)
=> {:controller=>"users", :action=>"index"}
Get just the controller-name
Rails.application.routes.recognize_path(app.users_path)[:controller]
=> "users"
Ability uses the Model for its breakdown, so convert from controller name to it's model (assuming default naming used) ...
Rails.application.routes.recognize_path(app.users_path)[:controller].classify
=> "User"
Get just the action-name
Rails.application.routes.recognize_path(app.users_path)[:action]
=> "index"
And since the "can?" method needs a Symbol for the action, and Constant for the model, for each menu-item we get this:
path_hash = Rails.application.routes.recognize_path(app.users_path)
model = path_hash[:controller].classify.constantize
action = path_hash[:action].to_sym
Then use our existing Abilty system to check if the current_user can access it, we have to pass the action as a symbol and the Model as a constant, so ...
<% if can? action model %>
<%= link_to "Users List", users_path %>
<% end %>
Now we can change who can see this resource and link from the Ability file, without ever messing with the menu, again. But to make this a bit cleaner, I extracted out the lookup for each menu-item with this in the app-controller:
def get_path_parts(path)
path_hash = Rails.application.routes.recognize_path(path)
model_name = path_hash[:controller].classify.constantize
action_name = path_hash[:action].to_sym
return [model_name, action_name]
end
helper_method :get_path_parts
... so I could do this in the view (I took out all the html-formatting from the links for simplicity, here):
<% path_parts = get_path_parts(users_path); if can?(path_parts[1], path_parts[0]) %>
<%= link_to "Users Listing", users_path %>
<% end %>
... and to make this not take all day typing these per-menu-item if-wraps, I used regex find/replace with capture and wildcards to wrap this around every list-item in the menu-item listing in one pass.
It's far from ideal, and I could do a lot more to make it much better, but I don't have spare-time to write the rest of this missing-piece of the Role/CanCan system. I hope this part helps someone out.
I'm trying to build a condition based on wether or not a "user" is a "member". Basically I need a way of checking if the current_user.id matches any of the user_id of any members. The non-working code I have right now is:
<% if current_user = #page.members %>
you can view this content.
<% end %>
I'm looking for something along the lines of: "If current_user.id exists in the "user_id" of any members."
Something like this, based on the field names in your question:
<% if #page.members.map(&:user_id).include? current_user.id %>
You can view this content
<% end %>
Assuming your #page.members variable contains an array, you can use the include? method:
<% if #page.members.include? current_user %>
you can view this content.
<% end %>
If you're using an array of ids, you will of course need to change the test slightly to look for the current user's id:
<% if #page.members.include? current_user.id %>
you can view this content.
<% end %>
#member_ids = #page.members.map{|m| m.id()}
then check for the condition as below
#memeber_ids.include? current_user.id()
Has said before include? should do the thing.
I'm just answering to tell you about a gem called CanCan, that gives you easy access for authorization "helpers".
Why you should use CanCan instead of doing what you are actually doing?
Don't reinventing the weel most of the times it's a goob practice.
You are placing business logic on the view (bad practice).
CanCan most likely has been developed thinking on security, and all the best practices in mind.
You save some developing hours.
Sorry if I repeated myself.
I've built several apps in rails that have submissions that you can vote on. Vote up or vote down. I've always custom built the voting functionality, and with each app, the code has gotten better and more elegant. But one part that has always been the same is when a user comes to a submission, I do the same thing in the view:
<% if #submission.votes.include?(current_user.votes) %>
"already voted on"
<% else %>
<%= link_to "vote", submission_vote_path(#submission) %>
<% end %>
Or something of this nature. I have a feeling there must be a more efficient way to go about this, but I'm not exactly sure how. Any advice?
It looks fine, but obviously it depends on the queries lying behing the scene.
It's always better to consume ruby code than db queries.
So if you have to check many votes, stick with your current code. Otherwise, make a dedicated query.