Rails create callback for 2 dependent resources - ruby-on-rails

I want to know how i can use create callback like before_create or after_create for the two dependent models.
class User < ActiveRecord::Base
end
class Member < ActiveRecord::Base
end
Lets suppose i have two models called User and Member and i want to create a member whenever any user will be created and want to create user whenever any member will be created .
If i will use the after_create or before_create callback in both the models it will run as never ending loop .so how this can be done.

Just check if either of the association exists in db before creating it in after_create callback, something like this:
class User < ActiveRecord::Base
after_create :create_member
private
def create_member
unless self.member?
# create member
end
end
end
class Member < ActiveRecord::Base
after_create :create_user
private
def create_user
unless self.user?
# create user
end
end
end

Related

Rails: filter association based on current_user class

Have an app where current_user can be a Client or and Admin. A Client can have many Accounts and an AccountStatement belongs to both the Account and the Client.
However there are some auto generated statements that we don't want to show the clients. I'd like to add a model scope of some kind that would look something like
def account_statements
if current_user.is_a?(Client)
super.where(auto_generated: false)
else
super
end
end
so if I ran .account_statements on a valid instance of Account or Client it would only return a subset of the statements if the current_user is a Client, but all of them if the current_user is an Admin. Is there any way to do this?
Just give each class its own method:
class Admin < ApplicationRecord
...
def account_statements
AccountStatement.all
end
end
class Client < ApplicationRecord
...
def account_statements
AccountStatement.where(auto_generated: false)
end
end
If you need Client to have a defined relationship to AccountStatement, things get a little more tricky:
class Client < ApplicationRecord
has_many :account_statements
# this method will supersede the relationship
def account_statements
...
end
end
In which case, you can either:
use a different name for the method
use this cool relationship-with-scope trick to do something like this:
class Client < ApplicationRecord
has_many :account_statements, -> { without_auto_generated }
end
class AccountStatement < ApplicationRecord
belongs_to :client
belongs_to :account
scope :without_auto_generated, -> { where(auto_generated: false) }
end

How can I create all has_one relationships automatically?

I have the following models:
class Post < ApplicationRecord
has_one: :basic_metric
has_one: :complex_metric
end
class BasicMetric < ApplicationRecord
belongs_to :post
end
class ComplexMetric < ApplicationRecord
belongs_to :post
end
Once a post is created, both basic_metric and complex_metric are nil:
p = Post.first
p.basic_metric # => nil
p.complex_metric # => nil
And because of how my app is going to work, the BasicMetricsController and ComplexMetricsController only have the update method. So I would like to know if there is a way to create them as soon as a post is created.
One very common way of accomplishing this is using ActiveRecord callbacks
class Post
after_create :create_metrics
private
def create_metrics
# methods created by has_one, suggested in the comments
create_basic_metric(additional_attrs)
create_complex_metric(additional_attrs)
end
end
Another option you have is to overwrite the method created by has_one, i.e.:
class Post
has_one: :basic_metric
has_one: :complex_metric
def basic_metric
super || create_basic_metric(additional_attrs)
end
def complex_metric
super || create_complex_metric(additional_attrs)
end
end
This way they won't be created automatically with any new post, but created on demand when the method is called.
Can you try this one,
post = Post.first
post.build_basic_metric
post.build_complex_metric
This will help you to build/create the has_one association object if record not saved by default use post.save at the end.
If you need this in modal you can use like this,
class Post
after_create :build_association_object
private
def create_metrics
self.build_basic_metric
self.build_complex_metric
# save # (optional)
end
end

Rails 4 + Devise: After create user, create XYZ

I have a User model that gets created through Devise, and after it's creation, I would like to automatically create a new Client (another model in my app). The new Client's atrribute, :user_id, should be equal to the :id of the User that was just created. I believe I need to use something like:
class Users::RegistrationsController < Devise::RegistrationsController
after_create :create_client
def create_client
Client.create(:user_id, :id) # Not sure what should go here
end
end
Is this the correct way to accomplish this? Also, if associations are important Client belongs_to :user and User has_one :client
You can add an after_create callback in User model(user.rb), check here for more information on how to create has_one associations.
class User < ActiveRecord::Base
after_save :add_client
def add_client
self.create_client(client_attribute1: value, client_attribute2: value)
end
end

Rails Methods that call themselves

In Rails is there a way that I can make a method call itself based on a change in the database? For instance, lets say I have two classes: Products and Orders.
Orders have three possible enum values:
class Order < ActiveRecord::Base
enum status: [:pending, :processing,:shipped]
belongs_to :products
end
I would like to batch process Orders so when a product has 50 orders, I want it to set all Orders associated with it to processed. Orders default to :pending. To change an order to :processing I would call order.processing!. I could write a method into the Products model like:
def process_orders
if self.orders.count=50
self.orders.each do |order|
order.processing!
end
end
The problem with this is that I would have to call the process_orders method for it to execute, is there anyway I could make it automatically execute once a product has 50 orders?
This is sounds like a good opportunity to use an Active Record Callback.
class Order < ActiveRecord::Base
belongs_to :product
after_save do
product.process_orders if product.pending_threshold_met?
end
end
class Product < ActiveRecord::Base
has_many :orders
def pending_threshold_met?
orders.where(status: :pending).count >= 50
end
end
I think you can use update_all to update the status column of all of your orders at once rather looping through them one by one:
self.orders.update_all(status: :processing)
and wrap that inside a callback.
Something like this:
class Order < ActiveRecord::Base
after_save do
product.process_orders if product.has_fifty_pending_orders?
end
# rest of your model code
end
class Product < ActiveRecord::Base
# rest of your model code
def process_orders
self.orders.update_all(status: :processing)
end
def has_fifty_pending_orders?
self.orders.where(status: :pending).count >= 50
end
end

how to avoid overwriting nested attributes?

I have:
class Activity < ActiveRecord::Base
has_and_belongs_to_many :balance_sheets
end
and
class BalanceSheet < ActiveRecord::Base
has_and_belongs_to_many :activities
accepts_nested_attributes_for :activities
end
When I perform an UPDATE sending "balance_sheet"=>{"activity_ids"=>["10", "20"]} if I have previous activities loaded on the balance_sheet object, the activities collection is replaced. I don't want override the activities old values, I want to add new ones. How can I do this?
Don't update the BalanceSheet object directly. Instead, create an intermediate BalanceSheetUpdater class which will take your activity_ids and append it to the existing balance_sheet's activity_ids.
class BalanceSheetUpdater
def initialize balance_sheet
#balance_sheet = balance_sheet
end
def call(balance_sheet_params)
new_ids = balance_sheet_params.delete("activity_ids")
update_status = #balance_sheet.update(balance_sheet_params)
if update_status
existing_ids = #balance_sheet.activity_ids
#balance_sheet.update(existing_ids + new_ids)
end
update_status
end
end
# controller
updater = BalanceSheetUpdater.new(#balance_sheet)
if updater.call(balance_sheet_params)
... # success / fail actions

Resources