Been experimenting a lot with Rails lately and I'm currently working on a project. What I wish to accomplish is to have one login form for 2 set of tables, in the same db.
One called user and the other called member. I'm on the path of making it so that if you register your email with one of them, you can't register with the other. To avoid that duplication bug if you sign up for both. However what I can't seem to figure out is how to create a form_for that checks if the login data is present in either user or member, then log them in respectively.
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
Not sure how to tweak this bugger.
You can overwrite Devise controllers and write your own custom create function to determine which model to call to save. You can follow the documentation here: https://github.com/plataformatec/devise#configuring-controllers
Another way I can propose is that you simply use devise controllers and only change the view. Since the forms are the same, you extract your form into a partial and then overwrite devise view files to use your partial. You can follow the documentation here to overwrite devise views: https://github.com/plataformatec/devise#configuring-views
Since your user and member are quite similar, you may want to consider subclassing your member from user (or the other way) so that the validation for email is in user and member will also gain that validation. User and member can share the same table.
For your login form, you can use the same form but you need to direct it to your own custom controller which will then checks the 2 models and then return the result. Follow the steps in overwriting devise controllers as above.
Related
I've a user profile (with name, logo, about_me) which is created after user creation(using Devise). Profile table uses user_id as Primary key.
Now I want that whenever the user creates/updates a post, while filling in form some details are taken from profile, so profile data or #profile be available in post form as I cannot expose my model in form.
To set post.myname attribute in create and #update I'm doing this:
#myprofile = Profile.find_by_user_id(current_user)
write_attribute(:myname, #myprofile.name)
I read from various sources but what's the best solution of the 4 given and if anyone can back with easy code as I do not want to do something extensive? Thanks in advance.
1)Form Hidden fields - Like get the profile data as above in hash in #edit and then pass through form and access fields in #update but that way we will pass each field separately. Can one #myprofile be passed?
2)Session - I feel if profile data is stored in a session and someone updates profile then updated data won't be available in that session.So not sure if it is plausible.
3)Caching - easy way to do that?
4)polymorphic profile---tried it but I didnot get relevant example. I was stuck with what to put as profileable id and type and how to use them in the code.
If your Profile and User models have a one-to-one relationship with each other, the simplest solution is to remove the Profile model altogether and move its fields into the User model.
Devise already queries the database to obtain the current_user object. So, your example would like this:
write_attribute(:myname, current_user.name)
Which wouldn't hit the database (after Devise has retrieved the current_user object).
If you're forced to keep the Profile model, in looking at your four scenarios ...
You could use a session variable. Something like:
session[:profile_name] ||= #myprofile.name
This would go in a controller action.
The trick here is that you will want to redefine the each relevant session variable if the profile gets updated. And because you don't have access to the session in the model, you'd be best to perform that action in the controller. So, not pretty, but it could work.
You could also use low-level caching, and save the profile relationship on the user. In general, you could have a method like this in your user model:
def profile_cached
Rails.cache.fetch(['Profile', profile.id]) do
profile
end
end
Here, too, you will have to know when to expire the cache. The benefit of this approach is that you can put this code in the model, which means you can hook its expiration in a callback.
Read more about this in Caching with Rails.
I would avoid hidden fields and I'm not sure how a polymorphic relationship would solve you not hitting the database. So, #2 and #3 are options, but if you can combine the two models into one, that should simplify it.
I am using form_for in the _form.html.erb view in order to create my form for both the edit and new actions, as per a standard scaffold.
I have a model Owner which has_many pets.
I would like to put an html link on my views/owners/show.html.erb to create a new pet for said owner. This link will point to the new action of pets_controller.rb which when accessed will render the view in pets/new.html.erb
What I want to happen is for the the owner_id to be passed with the link in the url to the new action of pets_controller.rb and then be used as the default for a collection_select in pets/new.html.erb
So I have a link to create a new pet but because that link was on a specific owner page, I want the form to create a new pet to have that owner already set, so the user does not have to select from the list.
This has to be done without changing the behaviour of the edit action/view in pets.
I know that I can pass GET arguments then access them in the controller via params, then create variables in the action which are passed to the view. I can then manually check for a default and set it in the view. I do not need assistance in coding if this is the only solution.
Is there is a better way to do this? A format with which I can pass the params such that the view will just pick them up? Without manually editing my controllers and views?
While my personal inclination would be to do as you said and pass a parameter in the link helper and then access the params array in the pets view, you may find that this is the perfect opportunity to explore Nested Resources. Essentially, you could declare owners/:owner_id/pets/:pet_id route with:
resources :owners do
resources :pets
end
You could then link to this route, and reference :owner_id without having to append the query string to the URI (making somewhat cleaner for reuse).
This is likely more work for you, but also potentially more extensible (and certainly more inline with the Rails way of doing things).
REVISION
Added the following regarding link helpers to the comments, but wanted to reflect it in the answer as well.
To show a pet should be:
<%= link_to owner_pet_path( owner_variable, pet_variable) %>
To view pets' index index should be:
<%= link_to owner_pet_path( owner_variable ) %>
The answer given to this question is fantastic.
As #ConnorCMcKee suggests it would be wise to consider nesting your routes. However, if you are a beginner as myself I found that it helped my learning to simply nest my second controller into the first (i.e. nest PetsController into OwnersController) as a first step. Then afterwards I would continue with the routes.
The method would be something like:
1./ In owners/index.html.erb:
Links to PetsController index action
The key to make this work is to send the :owner_id in your link parameters. Then that Pets index action will have access to that :owner_id and know which :owner_id called it.
2./ In PetsController you would then be able to find that Owner using that id, like so:
params[:owner_id]
Then your actions can start to take advantage of knowing what Owner called them. Remember though that all your redirects inside your PetsController need to preserve params[:owner_id]. That is because once you are inside that nested structure you have to maintain it and stay inside it and always know which :owner_id you are working with.
In my Rails 3.2 app there are two Devise models: User and Admin. I have a comment partial form that both can use to make comments on a Post. However, I have included conditional logic with the *_signed_in? helper provided by Devise so that a checkbox appears for admins that allows them to make their comment visible only to other admins. Form checkbox code:
- if admin_signed_in?
.pull-right
= label_tag :internal, "Private"
= f.check_box :internal
It's not a huge issue because it should never occur in production, but in development and staging I've noticed that if someone (tester, etc) is logged in as both an Admin and a User in different tabs on the same browser, the logic of my form doesn't work because (I guess?) those two tabs are using the same cookie/session info/whatever. The checkbox shows up on the User's form because the Admin is signed in on the other tab. It works fine if two different browsers are being used.
Is there a way to avoid this?
You may need to find or create a variable that can be used to determine if you're in the admin part of the site. A cheesy way would be to put a before filter in your admin controller(s) that sets an instance variable (#admin_site = true for example) then update your partial thus:
- if admin_signed_in? && #admin_site
.pull-right
= label_tag :internal, "Private"
= f.check_box :internal
However it's considered bad practice by some (Sandi Metz for example) to proliferate the instance variables sent to the view. Also, it's a good idea to pass parameters in to partials explicitly as locals rather than relying on instance variables. (This helps readability and makes it easier to share them in general).
The facade pattern can help here.
http://robots.thoughtbot.com/sandi-metz-rules-for-developers
Edit:
Since using the facade I tend to spurn helpers in general, but you could probably do something like this:
#app/helpers/application_helper.rb
module ApplicationHelper
def show_internal?
request[:controller].in? ['admin']
end
...
Assuming your admin actions are all in a controller called 'admin_controller'.
You can add more admin controllers if you have many, by adding to the array:
request[:controller].in? ['user_admin', 'product_admin']
I have both Pundit and Devise setup and working correctly in my rails app. However I am unsure about how to let the user decide their role when signing up.
At the moment:
I have a URL param which is passed to the Devise new view.
In the form_for I set a hidden field called role to the value of the param.
This works.
But I am concerned that a malicious user could change this param to say "Admin" and now they are an admin.
How should I handle this? I don't want to put a restriction in the model as that will cause issues when I want to create an admin. Should I override the devise registrations controller to put a check in there?
You don't need to override Devise's RegistrationsController for what you're trying to do.
If you want admins to be able to create users that have an arbitrary role set, you could simply use your own controller. Devise still makes it easy to create a user yourself, so you'll just have to make a controller handling this. Of course, don't forget to protect it using Pundit so only admins can use this functionality.
This approach still works if you use the Confirmable module. As no confirmation e-mail will be sent on user creation, though, you'll either have to call user.confirm! after saving the model to immediately unlock the account, or manually send the confirmation e-mail using user.send_confirmation_instructions.
Edit:
This Pundit policy may or may not work for what you're trying to do. You will have to override the create action of Devise's RegistrationsController here in order to use Pundit's authorize method. For dryness' sake, you should also move the roles list elsewhere, perhaps into the model.
class UserPolicy < Struct.new(:current_user, :target_user)
def create?
registration_roles.include?(target_user.role) if current_user.nil?
end
private
def registration_roles
%w(RED BLU Spectator)
end
end
After a fair amount of googling I have an answer. First stick some validation in your model for the roles Active Record Validations Guide: See 2.6 inclusion: validator option
After this your roles are validated to ensure they are correct, you could of course have a lookup table as well. Then you have two options:
Use a conditional before_save Callback for new records. Then check if the new record has the role your protecting and if so raise an error. To catch later (in an overridden devise controller (see second option).
Override the Devise registrations controller See this SO question. And put some checks in a completely overridden create action. Use the session to store the url param passed to the new action (also needs to be completely overridden). Then if the create action fails and redirects to new you still have access to the role in the session (as the param will be cleared from the URL unless you manipulate it).
So either way you need to override the registrations controller, its just a case of how much.
I suspect there is a way to do this with just Pundit. But I have yet to be able to get it to work.
I've a simple rails application; it has a devise authentication mechanism. Then i've added activeadmin, that brings its devise based authentication mechanism.
There are other question and answers about merging the two models. This question is about which is the setting that makes the two authentication realms distinct.
Example. I perform login in the admin page:
localhost:3000/admin
Here the user model is AdminUser.
then i try to move to a regular (non active-admin) page:
localhost:3000/documents
Here the user model is User.
here, if I test the current_user variable, it is nil and not an instance of AdminUser. That is: the two authentication areas (i used the word realm but I don't know if it is correct) are kept distinct.
I've searched in the activeamdin initializer, but i couldn't find a setting that contains the information of creating a distinct 'authentication realm'.
Update 1 (and possible answer):
They are not distinct.
If i test the current_admin_user, it contains and AdminUser instance.
you have two models User and AdminUser associated with two separate DB tables, right?
do you have separate AA and User model routes inside routes.rb?
finally, you have to set devise settings for User < AR::Base model (AA user model already shipped with activeadmin gem)
in this case, authentications through User and AdminUser models will be separated according to routes you set giving you 'realms' you asked for..
or i didn't get the question...
current_admin_user helper gives you AdminUser instance
current_mega_super_user helper would give you MegaSuperUser instance (by default)