I have a Rails 4 app that uses Devise.
I'm trying to let the User model alone so that I don't cross paths with Devise, so I created a Profile model for all my settings/views the user will have.
The Profile has
belongs_to :user
The User has
has_one :profile
Where do I put the logic that the app needs to know to create a profile upon new user creation, and delete it upon user deletion?
You can use Active Record Callbacks to create a profile. And you can use dependent: :destroy to make sure the profile will get destroyed when the user is destroyed.
In your models/user.rb
class User < ActiveRecord::Base
has_one :profile, dependent: :destroy # destroy user's profile
after_create :create_default_profile
... ...
private
def create_default_profile
Profile.create(user_id: self.id)
end
end
Rails associations have a dependent option, which handles deleting associated records.
User < ActiveRecord::Base
has_one :profile, dependant: :destroy
end
:destroy, when the object is destroyed, destroy will be called on its
associated objects.
:delete, when the object is destroyed, all its
associated objects will be deleted directly from the database without
calling their destroy method.
Creating the associated Profile can be done with ActiveRecord callbacks or in the controller. I have getting more and more weary of callbacks since its difficult to control when and where they are actually run.
In the case of Devise you would override the Devise::RegistrationsController.
Related
I've a Rails 4 app that uses Postgresql database. I'm using UUIDs as id for my models.
Everything works as expected, I'm trying to set a dependant destroy has many relation, and the "dependant destroy" is not working.
Is there any incompativility between postgress UUIDs and dependent destroy? I need to set foreign keys?
I expalin a bit of my code:
Navigation through models is working correclty
To define the has_many I'm using
has_many :some_models, dependent: :destroy
My migrations are something like:
def change
create_table :my_model, id: :uuid do |t|
To test, I'm using console. I create a relation, delete the "some_models" and the main model is not deleted.
Thanks
You are thinking of the association backwards. dependent: destroy means: When I destroy a parent record, destroy the children that are associated with that record. Here's a contrived example:
class User
has_many :photos, dependent: :destroy
end
When the user is deleted, you want their photos to also be deleted.
If you really want to delete a parent record when a child is deleted, you can do so from the before_destroy callback like so:
class Photo
before_destroy :delete_parent_user
def delete_parent_user
user.destroy if self.user
end
end
Note that other children may still be pointing to that parent record if this is a has_many relationship so this may not be advisable.
dependent: :destroy only destroys child records. When you destroy my_model record, all some_model records belonging to it will be destroyed.
I have a Customer model that has many Purchase Orders. In the model, before deleting a customer it checks to make sure that the customer has no associated PO's and prevents deletion if it does. Now I've used this almost exact same code on other models without a problem, but with this model if I try to delete a customer with PO's I get that white and red screen saying an ActiveRecord::RecordNotDestroyed error was thrown on if company.destroy instead of being redirected to the customer page with a nice flash warning.
model/company.rb
class Company < ActiveRecord::Base
belongs_to :user, foreign_key: :account_owner, counter_cache: true
has_many :purchase_orders
before_destroy :po_check
private
def po_check
!self.purchase_orders.any?
end
end
controllers/companies_controller.rb
class CompaniesController < ApplicationController
def destroy
company = Company.find(params[:id])
if company.destroy
flash[:success] = company.name + ' deleted.'
redirect_to companies_path
else
flash[:danger] = 'Cannot delete companies associated with Purchase Orders'
redirect_to company
end
end
end
It may be a Rails version issue. Rails 4.1.6 says it will raise that error if you do what you're doing.
You could also use :dependent to accomplish this. Example:
has_many :purchase_orders, dependent: :restrict_with_error
From the documentation:
:dependent Controls what happens to the associated objects when their
owner is destroyed. Note that these are implemented as callbacks, and
Rails executes callbacks in order. Therefore, other similar callbacks
may affect the :dependent behavior, and the :dependent behavior may
affect other callbacks.
:destroy causes all the associated objects to also be destroyed.
:delete_all causes all the associated objects to be deleted directly
from the database (so callbacks will not be executed).
:nullify causes the foreign keys to be set to NULL. Callbacks are not
executed.
:restrict_with_exception causes an exception to be raised if there are
any associated records.
:restrict_with_error causes an error to be added to the owner if there
are any associated objects.
Using devise as my authentication system I would like to build my profile on user registration.
I read many topics about this on SO, and decided to take the approach of building the profile within the model:
profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
attr_accessible :user_id, # FIXME This is secure?
end
user.rb
class User < ActiveRecord::Base
devise ...
has_one :profile
accepts_nested_attributes_for :profile
def build_profile
Profile.create(:user_id => id)
end
end
My two questions are:
Is having the user_id in attr_accessible dangerous (mass-assignement)?
Did I have to put my profile creation in a controller (registration create) using a transaction? (Here if my profile fails to build I have still a user record)
Is having the user_id in attr_accessible dangerous (mass-assignement)?
yes it is, you should avoid adding foreign keys to attr_accessible most of the time, although there's scenario that it's ok to use (if the association publicly accessible like countries for example) or if you override the setter and do some kind of check..
Did I have to put my profile creation in a controller (registration create) using a transaction? (Here if my profile fails to build I have still a user record)
just add validates_associated :profile in User model and maybe also validates :profile, presence: true (kinda forgot if validates_associated allow nil or not)
I have a Rails app with three kinds of users, professionals, students and regular citizens. I therefore made the User model polymorphic with three different kinds of profiles.
User.rb
belongs_to :profile, polymorphic: true
Professional.rb
has_one :user, as: :profile, dependent: :destroy
Student.rb
has_one :user, as: :profile, dependent: :destroy
Citizen.rb
has_one :user, as: :profile, dependent: :destroy
I want to use Devise as central sign up and created the devise for the User model
rails generate devise User
and then I created a Registrations controller that inherited from Devise Registrations controller, and in the after_sign_up_path_for method, I assigned the user to whichever of the profiles the user selected on the signup form.
class RegistrationsController < Devise::RegistrationsController
protected
def after_sign_up_path_for(user)
if user.professional === true
user.profile = ProfessionalProfile.create!
user.save!
elsif
....
end
end
Right now, this works, in that, by overriding the def after_sign_in_path_for(resource) in the Application controller, I can redirect users to whatever profile they created
def after_sign_in_path_for(resource)
if current_user.profile_type === 'ProfessionalProfile'
professional_profile_path(current_user)
elsif
....
end
However, even though this works, I have very little experience with Rails (in terms of making my own application; i've followed a few tutorials) and devise, so I'm wondering, before I continue on developing the app, if I'm going to run into problems either with devise or anything else by having created profiles that way. Is there a better way to do this?
I guess as one possible alternative I was wondering if I should try to override Devise's create user action, so that it creates the relevant Profile at the same time.
You can add columns to the user table by using the code:
rails generate migration add_professional_to_users professional:boolean
and similarly
rails generate migration add_student_to_users student:boolean
and also
rails generate migration add_citizen_to_users citizen:boolean
this is a better method according to me as a similar method is described by devise to create an admin, see it here :devise,option1
Similarly you may add these roles. Just an option but one that I feel is better.
I have a really simple association with the Devise user object where each user has one Profile (with more application specific stuff...) I am having no issues creating the User object and accessing the user and its profile object. i.e.,
#user.profile
However, I'm having an issues when I try to delete the profile object - I'd assume that when I delete the User object, it would also delete each associated object. The association in my User object is like so
accepts_nested_attributes_for :profile, :allow_destroy => true
The has_one and belongs_to associations are set on both the User and Profile objects. Maybe the issues is in Devise code - I'm stumped. An idea what I'm missing here.
You need to specify :dependent on the association:
has_one :profile, :dependent => :destroy
Look Association for more information.