I have a relationship many to many, I need the callback of destroy to be able to make a connection in another bank and delete an object there too, but the destroy is not executed when I delete an object from the join table
Sala
class Sala < ActiveRecord::Base
belongs_to :empresa
has_many :sala_participantes, dependent: :destroy
has_many :participantes, :through => :sala_participantes
end
Sala Participante:
class SalaParticipante < ActiveRecord::Base
belongs_to :sala
belongs_to :participante
belongs_to :confbridge_pin, dependent: :destroy
validates :sala, presence: true
before_create :generate_token
after_destroy :destroy_confbridge_pin
def destroy_confbridge_pin
self.confbridge_pin.destroy
end
def generate_token
token = 4.times.map{ SecureRandom.random_number(10)}.join
tk = SalaParticipante.where(token: token)
if !tk.present?
self.token = token
cp = ConfbridgePin.create! nome: self.participante.nome,
sala: self.sala.nome,
pin: self.token
self.confbridge_pin = cp
else
generate_token
end
end
end
Participante
class Participante < ActiveRecord::Base
belongs_to :empresa
has_many :sala_participantes
has_many :salas, :through => :sala_participantes
end
Related
I'm currently implementing pundit, where I am trying to identify whether or not a user has an admin role.
Issue
I'm trying to avoid creating a join_table between discounts and users, by leveraging the relationship between
discounts and attraction (a discount belongs to an attraction)
attractions and park (a park has_many attractions)
parks and users (many to many relationship, via a join_table).
--> However, I get the error message: undefined local variable or method `attraction' for #<DiscountPolicy::Scope:0x00007fa012ec6b70>
Question
I was wondering:
if it's even possible what I'm trying to do and if so
how will I be able to access the user?
Code
discount controller
def index
#user = current_user
if params[:attraction_id]
#attraction = Attraction.find(params[:attraction_id])
#discounts = #attraction.discounts
#discounts = policy_scope(#discounts)
else
#discounts = []
end
end
discount policy
class DiscountPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
# scope.where(user: user)
scope.joins(attraction: :discounts).where(discounts: { attraction_id: attraction.id }).joins(park: :attractions).where(attractions: { park_id: park.id }).joins(park: :user_parks).where(user_parks: { user_id: user.id })
else
raise Pundit::NotAuthorizedError
end
end
end
def index?
user.admin?
end
end
models
class Discount < ApplicationRecord
belongs_to :attraction
has_many :reservations
end
class Attraction < ApplicationRecord
belongs_to :park
has_many :discounts, dependent: :destroy
accepts_nested_attributes_for :discounts, allow_destroy: true
end
class Park < ApplicationRecord
has_many :attractions, dependent: :destroy
has_many :discounts, through: :attractions
has_many :user_parks, dependent: :destroy
has_many :users, through: :user_parks
accepts_nested_attributes_for :users, allow_destroy: true, reject_if: ->(attrs) { attrs['email'].blank? || attrs['role'].blank?}
end
class UserPark < ApplicationRecord
belongs_to :park
belongs_to :user
end
class User < ApplicationRecord
has_many :user_parks, dependent: :destroy
has_many :parks, through: :user_parks
enum role: [:owner, :admin, :employee, :accountant]
after_initialize :set_default_role, :if => :new_record?
def set_default_role
self.role ||= :admin
end
devise :invitable, :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable, :invitable
end
You need to have nested association joins. Here's what your scope should look like:
scope.joins(attraction: [park: :user_parks]).where(user_parks: { user_id: user.id })
You can go through the documentation to understand better.
I have a project where people need to trade credits between them.
I'm doing this
User model
class User < ApplicationRecord
after_create :create_bank_account
has_one :bank_account, inverse_of: :user, dependent: :destroy
has_many :account_transactions, inverse_of: :user, through: :bank_account
end
Bank model
class BankAccount < ApplicationRecord
belongs_to :user, inverse_of: :bank_account
validates :balance, presence: true, numericality: true
validates :user, presence: true
has_many :account_transactions, inverse_of: :bank_account, dependent: :destroy
accepts_nested_attributes_for :account_transactions
before_validation :load_defaults
def load_defaults
if self.new_record?
self.balance = 4.0
end
end
Account transactions model
class AccountTransaction < ApplicationRecord
after_initialize :set_default_status, if: :new_record?
after_commit :transfer, on: :create
belongs_to :bank_account, inverse_of: :account_transactions
enum transaction_type: [ :Received, :Sent ]
enum status: [ :Approved, :Canceled ]
def set_default_status
self.status ||= :"Approved"
end
private
def transfer
source_account = BankAccount.find(source)
target_account = BankAccount.find(target)
ActiveRecord::Base.transaction do
source_account.balance -= amount
target_account.balance += amount
source_account.save!
target_account.save!
end
end
end
The credit transfer is working fine.
But I need to create the records with the bank_id of each user.
How would i create those 2 account transactions records?
A record with bank_id of user A and another record with bank_id of user B.
I do not know if the best way to handle transactions between users would be this way
Why you need 2 records for AccountTransaction?
I strongly recommend you to save 2 bank_account ids in the AccountTransaction model, see:
# acount_transaction.rb
class AccountTransaction < ApplicationRecord
after_initialize :set_default_status, if: :new_record?
after_commit :transfer, on: :create
belongs_to :target_bank_account, foreign_key: 'target_id', class_name: 'BankAccount'
belongs_to :source_bank_account, foreign_key: 'source_id', class_name: 'BankAccount'
enum transaction_type: [ :Received, :Sent ]
enum status: [ :Approved, :Canceled ]
def set_default_status
self.status ||= :"Approved"
end
private
def transfer
ActiveRecord::Base.transaction do
self.source_bank_account.balance -= amount
self.target_bank_account.balance += amount
source_bank_account.save!
target_bank_account.save!
end
end
end
You just need to create a new migration adding the target_id and source_id to AcountTransaction.
If you do it, you will be able to save in just 1 record the source_account and the target_account, this makes more sense, what do you think?
Update
You should remove the bank_id and create 2 new foreing_keys: target_bank_id and source_bank_id for AccountModel.
In the BankAccount model, you can add the following lines:
# bank_account.rb
has_many incoming_transfers, foreign_key: 'target_bank_id', class_name: 'AccountTransaction'
has_many made_transfers
, foreign_key: 'source_bank_id', class_name: 'AccountTransaction'
After it, you can check for each BankAccount how many transfers are made and received!
You can see more about this kind of association clicking here!
I Using accepts_nested_attributes_for to update has_many nested tables, Why not update but insert
diaries_controller.rb
def update
#diary=Diary.find(params[:id])
if #diary.update(update_diary_params)
render_ok
else
render_err :update_error
end
end
def update_diary_params
params.require(:diary).permit(:date,:weather,:remark, :diary_pictures_attributes=> [:diary_picture,:clothing_picture,:id,:_destroy])
end
model/diary.rb
class Diary < ApplicationRecord
has_many :diary_pictures,dependent: :destroy
accepts_nested_attributes_for :diary_pictures,allow_destroy: true
end
model/diary_picture.rb
class DiaryPicture < ApplicationRecord
belongs_to :diary
validates_presence_of :diary
end
enter image description here
I am currently struggling with a has_many :through association in my project.
This is my model
class Group < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :users , through: :user_groups
end
class User < ActiveRecord::Base
has_many :user_groups ,dependent: :destroy
has_many :groups , through: :user_groups
end
class UserGroup < ActiveRecord::Base
belongs_to :user , inverse_of: :placements
belongs_to :group , inverse_of: :placements
validates :level , presence: true
end
So when i tried to create new group but it didn't work out.
This is my controller
class GroupController < ApplicationController
def create
group = Group.new(group_params)
group.users << User.find_by(id: current_user.id)
if group.save
render json: group, status: 201, location: [group]
else
render json: { errors: group.errors }, status: 422
end
end
private
def group_params
params.require(:group).permit(:name, :shuttle_price, :court_price)
end
end
But when i call create method i got this error.
Could not find the inverse association for group (:placements in Group)
On this line
group.users << User.find_by(id: 6)
So how can i fix this?
Thanks!
Remove :inverse_of
class UserGroup < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :level , presence: true
end
You don't need to add inverse_of there. read this when to use inverse_of
UPDATE
I have an action in my Miniatures model called set_gold_and_silver.
I want my Users model to run it when a User is destroyed, so I have before_destroy :set_gold_and_silver in my User model.
A User has many Imagevotes. Before destroy I need to delete those Imagevotes and then run set_gold_and_silver on all the Miniatures that those imagevotes pertained to.
This is what I've got so far and I'm currently getting undefined method 'miniatures'.
It's not clear to me whether I am caching self.imagevotes or whether they are just deleted and then I get the error because they no longer exist?
def set_gold_and_silver
votes = self.imagevotes
self.imagevotes.destroy
votes.miniatures.uniq.each(&:set_gold_and_silver)
end
My models
User
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
has_many :collections, dependent: :destroy
has_many :miniatures, through: :collections
has_many :imagevotes, foreign_key: "voted_id", dependent: :destroy
has_many :imagevotes, foreign_key: "voter_id", dependent: :destroy
before_destroy :set_gold_and_silver
def set_gold_and_silver
my_collections = self.collections.each
their_miniatures = collection.miniature.uniq
my_collections.their_miniatures.each(&:set_gold_and_silver)
end
end
Miniature
class Miniature < ActiveRecord::Base
has_many :collections, dependent: :destroy
has_many :users, :through => :collections
has_many :imagevotes, dependent: :destroy
def set_gold_and_silver
wipe = self.collections.all
wipe.each {|s| s.update_attributes :is_gold => false, :is_silver => false}
top_collections = self.collections.limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
end
Collection
class Collection < ActiveRecord::Base
default_scope order('imagevotes_count DESC')
belongs_to :miniature
belongs_to :user
has_many :imagevotes, dependent: :destroy
end
Imagevote
class Imagevote < ActiveRecord::Base
belongs_to :collection, :counter_cache => true
belongs_to :voter, class_name: "User", :counter_cache => "voted_count"
belongs_to :voted, class_name: "User", :counter_cache => "vote_count"
belongs_to :miniature
after_create :set_gold_and_silver
after_update :set_gold_and_silver
def set_gold_and_silver
self.miniature.set_gold_and_silver
end
end
You need to make your code simpler:
class Miniature < ActiveRecord::Base
def set_gold_and_silver
self.collections.update_all("is_gold = false, is_silver = false")
top_collections = self.collections.limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
end
class User < ActiveRecord::Base
def set_gold_and_silver
self.miniatures.uniq.each(&:set_gold_and_silver)
end
end
you have has_many :miniatures, through: :collections so you don't need to work with collections to get minuatures.
And for now your code not working because everything still there before destroy. It need to be done after, when everything depended to user removed. And also as it seems for me you need to remove imagevotes in user destroy and set_gold_and_silver only after that. For now it's not done, so gold and silver stays.