Deleting a User relation using Rails Models and Interactors - ruby-on-rails

I have a question regarding the deletion of a User. Note that I'm not using Devise since my app is an API.
What I need to do
So I have a User model, I can delete this User with no issues. The user belongs to many other associations regarding Bank Accounts, Transactions, you name it.
When I delete the User, it's able to be deleted but its associations are not. Note that I'm using soft_deletion which means it gets in an INACTIVE state. And by saying that the "associations aren't being deleted" means that I just need to DISABLE specific associations when the User has been deleted or gets INACTIVE
What I currently have
user.rb model file
class User < ApplicationRecord
has_many :bank_accounts
def soft_deletion!
update!(status: "DELETED",
deleted_at: Time.now)
end
end
delete_user.rb interactor file
module UserRequests
class DeleteUser < BaseInteractor
def call
require_context(:id)
remove_user
context.message = 'user record deleted'
end
def remove_user
user = context.user
context.user.soft_deletion! #<- This is the method I have on my model, which works!
#below I removed all user invites in case there's one
user_invite = UserInvite.joined.where(
"lower(email) = '#{user.email.downcase}'"
)&.first
user_invite&.update(status: "CANCELLED")
return if user.user.present?
user.update!(status: "INACTIVE")
end
end
end
So giving a bit more context of what happened up there. My App is an API, on my frontend I remove the user and it actually works, and so what I need to do next is delete the user AND delete a bank_account that's associated with my user.
What I've been trying to do (This fails so hard, and I need some help )
I honestly don't know how to interact between interactions on Rails, that's the reason of my question here.
delete_user.rb interactor file
module UserRequests
class DeleteUser < BaseInteractor
def call
require_context(:id)
remove_user
soft_delete_transaction_account #method to delete bank account
context.message = 'user record deleted'
end
#since there's an association I believe in adding a method to verify if there's a bank
account.
def soft_delete_bank_account
context.account = context.user.bank_accounts.find_by_id(context.id)
fail_with_error!('account', 'does not exist') unless
context.account.present?
context.account.update!(deleted: true,
deleted_at: Time.now)
end
def remove_user
user = context.user
context.user.soft_deletion! #<- This is the method I have on my model, which works!
context.user.soft_delete_transaction_account #<- Then I add that method here so the bank account can be deleted while the user gets deleted!
#below I removed all user invites in case there's one
user_invite = UserInvite.joined.where(
"lower(email) = '#{user.email.downcase}'"
)&.first
user_invite&.update(status: "CANCELLED")
return if user.user.present?
user.update!(status: "INACTIVE")
end
end
end
ERROR LOG of my code:
NoMethodError - undefined method `soft_delete_bank_account' for #<User:0x00007f8284d660b0>:
app/interactors/admin_requests/delete_user.rb:47:in `remove_user'
app/interactors/admin_requests/delete_user.rb:9:in `call'
app/controllers/admin_controller.rb:18:in `destroy'
I would appreciate your help on this!

Related

ActiveRecord is not reloading nested object after it's updated inside a transaction

I'm using Rails 4 with Oracle 12c and I need to update the status of an User, and then use the new status in a validation for another model I also need to update:
class User
has_many :posts
def custom_update!(new_status)
relevant_posts = user.posts.active_or_something
ActiveRecord::Base.transaction do
update!(status: new_status)
relevant_posts.each { |post| post.update_stuff! }
end
end
end
class Post
belongs_to :user
validate :pesky_validation
def update_stuff!
# I can call this from other places, so I also need a transaction here
ActiveRecord::Base.transaction do
update!(some_stuff: 'Some Value')
end
end
def pesky_validation
if user.status == OLD_STATUS
errors.add(:base, 'Nope')
end
end
end
However, this is failing and I receive the validation error from pesky_validation, because the user inside Post doesn't have the updated status.
The problem is, when I first update the user, the already instantiated users inside the relevant_posts variable are not yet updated, and normally all I'd need to fix this was to call reload, however, maybe because I'm inside a transaction, this is not working, and pesky_validation is failing.
relevant_users.first.user.reload, for example, reloads the user to the same old status it had before the update, and I'm assuming it's because the transaction is not yet committed. How can I solve this and update all references to the new status?

Rails getting access to current user

I'm trying to save in Note which Employee was the last editor of a `Note'
In a View, I'm able to access the current employee like this:
<h4><%= current_user.employee.id%></h4>
But, in a Model, I can't use current_user.employee.id.
So, I'm trying this in the User model:
def self.current
Thread.current[:user]
end
def self.current=(user)
Thread.current[:user] = user
end
And this in the Note model:
before_create :record_update
before_update :record_update
protected
def record_update
self.lasteditor_id = User.current.employee.id unless User.current.employee.id.nil?
end
What I'm getting is the last User in the Users table.
Thanks for the help!
current_user gets the logged in user information from the session. You cannot access session variables from model. If you want to update the Note model with the Last employee who viewed it, do it in your controller(most likely show action of your note or any other action you think would be right)
def show
#note = Note.find(params[:id])
#note.update_atribute(:last_viewed_by, current_user.id)
end
You code might look different from above. But this is the idea

Devise/OmniAuth not signing in some users

Our product is a Rails application; authentication is handled with Devise and OmniAuth. ~2000 users total. We've recently had reports of some users not being able to sign in, but we can't figure out why. Not getting any server errors or anything in our production logs to suggest anything is awry.
Let's look at some code…
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
...
def twitter
oauthorize "twitter"
end
private
def oauthorize(provider)
if env['omniauth.auth']
#identity = Identity.from_omniauth(env['omniauth.auth'])
#person = #identity.person
# 1. failing here? Maybe?
if #person
PersonMeta.create_for_person(#person, session[:referrer], session[:landing_page]) if #person.first_visit?
# 2. PersonMetas *aren't* being created.
flash[:notice] = I18n.t("devise.omniauth_callbacks.success", kind: provider)
sign_in_and_redirect(#person, :event => :authentication)
# 3. Definitely failing by here…
else
redirect_to root_url
end
else
redirect_to root_url
end
end
end
class Identity < ActiveRecord::Base
belongs_to :person, counter_cache: true, touch: true
after_create :check_person
def self.from_omniauth(auth)
where(auth.slice("provider", "uid")).first_or_initialize.tap do |identity|
identity.oauth_token = auth['credentials']['token']
identity.oauth_secret = auth['credentials']['secret']
case auth['provider']
when "twitter"
identity.name = auth['info']['name']
identity.nickname = auth['info']['nickname']
identity.bio = auth['info']['description']
identity.avatar_address = auth['info']['image']
else
raise "Provider #{provider} not handled"
end
identity.save
end
end
def check_person
if person_id.nil?
p = create_person(nickname: nickname, name: name, remote_avatar_url: biggest_avatar)
p.identities << self
end
end
def biggest_avatar
avatar_address.gsub('_bigger', '').gsub('_normal', '') if avatar_address
end
end
class PersonMeta < ActiveRecord::Base
attr_accessible :landing_page, :mixpanel_id, :referrer_url, :person_id
belongs_to :person
def self.create_for_person(person, referrer, landing_page)
PersonMeta.create!(referrer_url: referrer, landing_page: landing_page, person_id: person.id)
end
end
So we have that, and we're not getting any errors in production.
Where do we start? Well, let's see if the point of failure is Identity.from_omniauth
This method searches for an existing identity (we've written extra code for more providers, but not implemented client-side yet). If no identity is found it will create one, and then create the associated Person model. If this was the point of failure we'd be able to see some suspiciously empty fields in the production console. But no - the Person & Identity models have all been created with all of the correct fields, and the relevant bits of the app have seen them (e.g. their 'user profile pages' have all been created).
I just added in the if #person to the #oauthorize - we had one 500 where #identity.person was nil, but haven't been able to replicate.
Anyway, the real-world users in question do have complete models with associations intact. Moving down the method we then create a PersonMeta record to record simple stuff like landing page. I'd have done this as an after_create on the Person but I figured it wasn't right to be passing session data to a model.
This isn't being created for our problematic users. At this point, I'm kind of stumped. I'm not sure how the create ! (with bang) got in there, but shouldn't this be throwing an exception if somthing's broken? It isn't.
That is only called if it's a person's first visit anyway - subsequent logins should bypass it. One of the problematic users is a friend so I've been getting him to try out various other things, including signing in again, trying different browsers etc, and it keeps happening
so anyway, after spending 45 minutes writing this post…
One of the users revoked access to the app via Twitter and reauthenticated. Everything works now.
What the hell?
His old identity had his OAuth tokens etc stored properly.
Luckily this is resolved for one user but it's obviously an ongoing problem.
What do we do?
Is it possible that the identity.save line in Identity.from_omniauth is failing silently? If so, your after_create hook won't run, #identity.person will be nil, and you'll just (silently) redirect.
Try identity.save! ?

How can I add this transaction to when after email confirmation is completed with Devise?

Assuming that an existing user(ID:1367) can send this url to his friends as an invitation.
http://example.com/users/sign_up?invitation=1367
Then Users table has hidden column called invitation.
and This is procedure how it works that I want.
His friend input information to sign up.
He hits submit button then hidden field 'invitation' will be also sent to form.
1367 will be set in the column called 'invitation' of the record.
He will receive confirmation mail, and when he clicks on the link, I'd like to add this transaction, and execute only once for his account.
Of course, this shouldn't be executed when the existing user tried to re-activate.
Only for the first confirmation for the new user.
code
#user = User.find_by_invitation(current_user.invitation)
#user.friends = #user.friends + 1
#user.save
I already have registration controller that helps adding extra transaction to Devise.
Now I want to know how I can implement this procedure to my app.
Should it be something like this?
registrations_controller.rb
def after_????????
if #user = User.find_by_invitation(current_user.invitation)
#user.friends = #user.friends + 1
#user.save
end
end
You can do this on the user model. Use a callback called after_create which is triggered after a user is created.
# user.rb
after_create :handle_invitation_code
private
def handle_invitation_code
# do something with invitation here
# i'm assuming that you want to credit
# whoever it is that invited this user
# assuming that invitation contains the
# id of the referrer
if referrer = User.find_by_id(invitation)
# do something with referrer
end
end
Be warned that if you return false on a callback, it will cause a rollback and the record won't be saved.
UPDATE: callback for after confirmation
instead of using after_create, use before_save with an if option
before_save :handle_invitation_code, if: :just_confirmed?
def just_confirmed?
confirmed_at_changed? && confirmed_at_was.nil?
end
def handle_invitation_code
...
end

Overriding ActiveRecord class method

I am setting up a User model at the moment and I have a setup where a new user is emailed an activation token. When they click on the link the controller method that is called has the line
#user = User.find_by_activation_token! params[:activation_token]
Now my activation token has a 24 hour expiry associated with it and if it has expired I want the user record destroyed. This would be easy enough for me to implement in the controller but I'm trying to be a better Rails developer and a better Ruby programmer so I thought I should put this in the model (skinny controller, fat model!). I thought it would also give me better insight into class methods.
I have made several attempts at this but have been quite unsuccessful. This is my best effort so far;
def self.find_by_activation_token!(activation_token)
user = self.where(activation_token: activation_token).first #I also tried User.where but to no avail
if user && user.activation_token_expiry < Time.now
user.destroy
raise ActivationTokenExpired
else
raise ActiveRecord::RecordNotFound
end
user
end
Do I have to change much to get this to do what I want it to do, or am I on the wrong track entirely?
I think I got this. Your condition logic is a bit off
def self.find_by_activation_token!(activation_token)
user = self.where(activation_token: activation_token).first #I also tried User.where but to no avail
# if this user exists AND is expired
if user && user.activation_token_expiry < Time.now
user.destroy
raise ActivationTokenExpired
# otherwise (user does not exist OR is not expired)
else
raise ActiveRecord::RecordNotFound
end
user
end
I think it should be more like this:
def self.find_by_activation_token!(activation_token)
user = self.where(activation_token: activation_token).first #I also tried User.where but to no avail
raise ActiveRecord::RecordNotFound unless user
if user.activation_token_expiry < Time.now
user.destroy
raise ActivationTokenExpired
end
user
end

Resources