Single Table Inheritance routing? - ruby-on-rails

I have single table inheritance working just fine for my app. I have two user subtypes (Athlete and Company) that inherit the super-type User.
Let's say I am listing all users, and want a link to each user's profile from this list. I want to link to the athletes controller if the type is athlete and the companies controller if the type is company. Is there a standard Rails way to this? Maybe some routing tricks?

you can even do that much simpler, Rails recognizes which type of user it has to deal with, so let's say you have the instance variable #user wich can either be an Athlete or a Company, you can just do that
= link_to "Profile", #user
BAM! Rails magic!

<% User.find(:all).each do |user| %>
<%= link_to "user", eval("#{user.type.underscore}_path(user)") %>
<% end %>
This will generate a path according to the type of the user (stored in type field). Don't forget to add the type of users to your routes configuration.
I hope this helps.
regards

Related

Ruby on Rails - Single Sign Up and Login Form for Two Different User Types and handling the Session

I'm currently pretty new to Ruby on Rails, but right now I'm trying to set up a simple platform to create events, manage those events, and purchase tickets to those events. I have two different user types:
Manager:
has_many: events
(with params):
email
password
organization_name
Fan:
has_many: tickets
(with params):
email
password
name
cell_phone
I have two different partials containing sign-up forms for both Managers and Fans. My thought process right now is to have a param called #is_manager in the session that allows my sign-up form to dynamically hide/reveal the partials and to handle logic in the controller.
The sign-in form for both models will be identical, as they can both login using their emails and passwords. My current thought for this is to either include an additional checkbox which filters attempted logins to either the Fan or Manager database, or to require emails to be unique across both databases.
I've looked at a large number of other stack overflow questions, and have looked into Devise (which I was cautioned against while I still don't have a strong handling of Ruby on Rails), as well as some JQuery solutions to dynamically changing this session param, but I haven't found a solution which I feel applies well to me.
Thoughts?
(My current sign-up form code is something like below:)
<h1>Signup</h1>
<h3>Are you a manager?</h3>
<%= link_to_function "Yes", "magic_javascript_function" %>
<%= link_to_function "No", "magic_javascript_function" %>
<%= form_for :session, url: signup_path, method: :post do |f| %>
<%= render 'manager_signup', :f => f if #is_manager %>
<%= render 'user_signup', :f => f unless #is_manager %>
<%= f.submit 'Submit', class: "btn btn-primary" %>
<% end %>
If your new to RoR I highly advise going to The Rails Tutorial by Michael Hartl's railstutorial.org.
I recomend this tutorial because he shows how to create a user model with login sessions etc. without using devise or cancan and this gives excelent insight into what those programs are used for and how they work so you can use them later.
In regards to your specific case I would argue this: What if a member who has only acted as a manger wants to go to another managers event. Would he then need a separate fan account. Alternatively what if a member who has only been a fan wants to organize a local event for his garage band.
See where I am going with this. It might be best for a login/signup page to be agnostic towards these roles.
Where you could use these roles however is in you relationships
here you could do something like this
User.rb
has_many: events
has_many: tickets
Event.rb
belongs_to: manager, class_name: "User"
has_many: tickets
Ticket.rb
belongs_to: event
belongs_to: fan, class_name: "User"
Check the belongs_to api if it this gives you trouble you may need to explicitly set the foreign key.

Restricting a model to only view their own items in the has_many

So I'm trying to think about how to route my site and I need a little help. I have a business who can .build (as in business creates) buildings (sorry for the repetition haha) in a has_many. Each property has many something else.
I would like it so even though there will be more than one building, each business should only be able to view their own buildings, so if someone tries to alter a url, it would redirect home.
I have
resources :buildings
so as it is set up, anyone could just type in
host/buildings/whatever
I would like to redirect with an error if the building ID does not belong to the current_business (devise) it will redirect to their home page. each building has a business_id
Would I have to break the RESTful for this?
Thank you!
Assuming you have user_id in builduing resource:
buildings_controller.rb
def index
#buildings = current_user.buildings
end
def show
#building = current_user.buildings.find(params[:id])
end
buildings/index.html.erb
<% #buildings.each do |building| %>
<%= building.whatever_atribute %>
<% end %>
buildings/show.html.erb
<%= #building.whatever_atribute %>
With the above code when user will go to /buildings he will see only his buildings, and if he'll go to buildings/3 he will see this building if he owns it, in other case he will see a not found error that you can customize it with a redirect or display a styled page.

Ruby on Rails - Dropdown with Has Many relationship

I have four tables in my database. One is users, another is organizations. One user can have many organizations, and one organization can have many users. This relationship is stored in a third table, called user_organizations, with columns user_id and organization_id.
The fourth table is called organization_details, where I store additional, multi-row information about the organization.
What I want to happen is this: when a signed in user wants to add organization details to an org they are linked to through user_organizations, only the organizations they are linked to should appear in the dropdown list.
I am unsure of how to go about this. The system returns information about the signed in user through current_user, for example <%= current_user.first_name %>.
So I'm trying to do something like this:
collection_select(:organization_detail, :organization_id, Organization.where({id: current_user.id...something something about user_organization being here too}), :id, :name_and_state)
What is the best way to approach this? Thank you very much!
You should be able to use the built in association. Since User has_many Organizations you can call current_user.organizations like this:
collection_select(:organization_detail, :organization_id, current_user.organizations, :id, :name_and_state)
This assumes you have everything hooked up correctly in the models.
You should also check out the Rails Guide to associations if you are new to them.
current_user.organizations will do the trick.
your dropdown would look something like this:
<ul>
<%- current_user.organizations.each do |organization| -%>
<li><%= link_to organization.name, your_path_here %></li>
<%- end -%>
</ul>

How to hide parts of the view given a user role on Rails 4

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.

Rails - passing parameters in link_to to hidden form field for id

I've got an application where the user can set up a folder to keep notes in. What I had previously was a hidden form field to store the id of the person who created it, i.e.:
<%=f.hidden_field 'user_id', :value => current_user.id %>
However, I now need to add a 'keyholder', who has read-only access to this folder. I have a list of links, which only appear if the user has added a folder, or the keyholder can set one up for them.
The keyholder is a regular user themselves, so the above code would only set up a folder with their own id, not that of the person whose account they are accessing. The keyholder has an 'access_id' that matches the user id of the the person whose account they can access.
How do I set it up so that the form is capturing the right user id?
What I'm trying to acheive is the following (this doesn't work, but might give a better idea of what I mean):
<% if current_user.access.folder.nil? %>
<li><%= link_to 'Create a Folder',
new_folder_path(:user_id => current_user.access_id) %></li>
<% end %>
And what would I need to change in the folder form partial to get it to accept this user id? Thanks!
You'd better use an authorization gem such like cancan
I'm not sure i completely understand what you're trying to do, but rather than storing the user id in a hidden field. Just use <%= current_user.id %> on any page as it seems your doing.
Then depending on how your models are setup, just create a helper method to check access of the page that the user is on. I'm assuming your helper will check the params[:id] or params[:user_id] to get the current page.

Resources