Adding an additional model relationships using build() on Rails - ruby-on-rails

Currently I have the following four models:
users <-- agency_memberships --> agencies
|
|
agency_roles
agency_memberships is a join table, and agency_roles is a small lookup table with the roles :
ID NAME
-----------
1 admin
2 editor
...
Before adding the AgencyRole model, when a user was created, if a param create_agency = true, then this was enough to create a new Agency (along the join table AgencyMembership).
# user_controller.rb
def create
#user = User.new(user_params)
#user.agencies.build(name: "Demo Agency") if params[:user][:create_agency]
if #user.save!
...
end
However, now I need to add a valid AgencyRole before saving.
Is it possible to do so with .build() or what is the Rails best practice to do so?
Right now, I'm creating all relationships manually before saving, which works but isn't as compact:
def create
#user = User.new(user_params)
if (params[:user][:create_agency])
agency_name = params[:user][:agency_name]
agency_role = AgencyRole.find_by(name: 'admin')
#agency_membership = AgencyMembership.new(agency_role: #agency_role)
#agency = Agency.new(name: agency_name)
#agency_membership.agency = #agency
#agency_membership.user = #user
#agency_membership.agency_role = agency_role
#user.agency_memberships << #agency_membership
end
if #user.save!
...
end
EdIT: My model relationships are as follows:
class AgencyMembership < ApplicationRecord
belongs_to :agency
belongs_to :user
belongs_to :agency_role
end
class AgencyRole < ApplicationRecord
validates :name, uniqueness: { case_sensitive: false }, presence: true
end
class Agency < ApplicationRecord
has_many :agency_memberships
has_many :projects
has_many :users, through: :agency_memberships
end
class User < ApplicationRecord
has_many :agency_memberships, inverse_of: :user
has_many :agencies, through: :agency_memberships
end

You can encapsulate and separate it from the controller, keep thin controller fat model, beside that you can use autosave in order to auto save association.
class AgencyMembership < ApplicationRecord
belongs_to :agency_role, autosave: true
end
class Agency < ApplicationRecord
has_one :agency_membership, autosave: true
end
module BuildAgency
def build_with_role(attributes = {}, &block)
agency_role_name = attributes.delete(:role)
agency = build(attributes, &block) # association: user - membership - agency
# add role
agency_role = AgencyRole.find_by(name: agency_role_name)
agency.agency_membership.agency_role = agency_role # autosave
agency
end
end
class User < ApplicationRecord
has_many :agencies, autosave: true, :extend => BuildAgency
def build_agency(attributes)
new_agency = agencies.build_with_role(attributes)
# ...
new_agency
end
end
# controller
def create
if (params[:user][:create_agency])
#user.build_agency(name: params[:user][:agency_name], role: params[:user][:agency_role])
end
if #user.save! # it'll save agencies since we set `autosave`
end

Related

Adding `or` on `has_many` association

I have these models and I want to get all the addresses of both customer_x and customer_y through the assoc has_many :addresses.
Is there a method or something that can modify the has_many :addresses to add a codition OR in the query?
# customer.rb
class Customer < ApplicationRecord
has_many :addresses
end
# customer_x.rb
class CustomerX < Customer
has_many :customer_ys
end
# customer_y.rb
class CustomerY < Customer
belongs_to :customer_x, foreign_key: :customer_x_id
end
# address.rb
class Address < ApplicationRecord
belongs_to :customer
end
I tried this but of course it will only return all the addresses belonging to customer_id 1.
customer = Customer.first
customer.addresses
=> SELECT * FROM addresses WHERE customer_id = 1
What I want is to add OR in the condition statement like this:
=> SELECT * FROM addresses WHERE customer_id = 1 OR customer_x_id = 2
customer_x = Customer.find(1)
customer_y = Customer.find(2)
This will give you the addresses of customer_x or customer_y
address_of_custormers_x_or_y = customer_x.addresses.or(customer_y.addresses)
I guess this design looks very complicated. If You still want to use same way then take a look at following hack.
It works if you are not using STI here.
# customer.rb
class Customer < ApplicationRecord
has_many :addresses
def addresses
if self.type == 'customer_x'
adrs = []
self.customer_ys.each do|c|
adrs << c.addresses
end
adrs << super
return adrs
else
super
end
end
end
# customer_x.rb
class CustomerX < Customer
has_many :customer_ys
end
# customer_y.rb
class CustomerY < Customer
belongs_to :customer_x, foreign_key: :customer_x_id
end
# address.rb
class Address < ApplicationRecord
belongs_to :customer
end
else if you are using STI. You need to move the addresses method to customer_x.rb class, as follows
# customer.rb
class Customer < ApplicationRecord
has_many :addresses
end
# customer_x.rb
class CustomerX < Customer
has_many :customer_ys
def addresses
adrs = []
self.customer_ys.each do|c|
adrs << c.addresses
end
adrs << super
adrs
end
end
# customer_y.rb
class CustomerY < Customer
belongs_to :customer_x, foreign_key: :customer_x_id
end
# address.rb
class Address < ApplicationRecord
belongs_to :customer
end
But if you observe carefully the addresses method, we are making one query for every customer_y for addresses to fetch. Instead if you want you can change that method to following way.
def addresses
cus_ids = self.customer_ys.pluck(:id)
cus_ids << self.id
Address.Where(customer_id: cus_ids)
end
JFYI: Its my friend Rahul's solution.
You can use simple where condition on address like below,
Address.where(customer_id: :customer_ids, customer_ids: [1,2])

Saving Rails associations after creating User

I'm new to Rails and ActiveRecord and need some help. Basically, I have 4 models: User, Property, PropertyAccount, and AccountInvitation. Users and Properties have a many to many relationship via PropertyAccounts. AccountInvitations have a user's email and a property_id.
What I want to happen is that after a user registers on my app, his user account is automatically associated with some pre-created Properties. What I don't know how to do is write the query to get the Property objects from the AccountInvitations and save them to the User object. Please see def assign_properties for my pseudo code. Any help is welcome, thanks so much!
class User < ActiveRecord::Base
has_many :property_accounts
has_many :properties, through: :property_accounts
after_create :assign_properties
# Check to see if user has any pre-assigned properties, and if so assign them
def assign_properties
account_invitations = AccountInvitations.where(email: self.email)
if account_invitations.any?
account_invitations.each do |i|
properties += Property.find(i.property_id)
end
self.properties = properties
self.save
end
end
end
class AccountInvitation < ActiveRecord::Base
belongs_to :property
validates :property_id, presence: true
validates :email, uniqueness: {scope: :property_id}
end
class Property < ActiveRecord::Base
has_many :account_invitations
has_many :property_accounts
has_many :users, through: :property_accounts
end
class PropertyAccount < ActiveRecord::Base
belongs_to :property
belongs_to :user
end
Thanks to #wangthony , I looked at the includes method on http://apidock.com/rails/ActiveRecord/QueryMethods/includes and tweaked one of their examples in order to get this to work. Here's the solution:
def assign_property
self.properties = Property.includes(:account_invitations).where('account_invitations.email = ?', self.email).references(:account_invitations)
self.save
end
I believe you can do this:
user.properties = Property.includes(:account_invitations).where(email: user.email)
user.save

Get Orders not Liked by logged in User Model in Rails 4

I have 3 Models: User, LikeOrder and Like. User has many LikeOrders. A User can like a LikeOrder only once. So I created Models as below:
class User < ActiveRecord::Base
has_many :like_orders
accepts_nested_attributes_for :like_orders
has_many :likes, dependent: :destroy
end
class LikeOrder < ActiveRecord::Base
belongs_to :user
has_many :likes, dependent: :destroy
end
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :like_order
end
And Migration for Like Model is:
class CreateLikes < ActiveRecord::Migration
def change
create_table :likes do |t|
t.references :user, index: true, foreign_key: true
t.references :like_order, index: true, foreign_key: true
t.timestamps null: false
end
end
end
So when User likes a LikeOrder I do it this way (using likes method directly) without any problem:
class User < ActiveRecord::Base
...
def like(order)
likes.create(like_order: order) if likes.where(like_order: order).count == 0
end
end
class UserController < ApplicationController
def like
#user = User.find(params[:user_id])
#order = LikeOrder.find(params[:order_id])
#user.like #order
end
end
There was no problem.
My Problem is I want to get Orders that:
their status are pending and the id is greater that from_id param and are not liked by logged in User.
class LikeOrder < ActiveRecord::Base
...
def self.not_likeds(user, from_id)
joins(:likes).where("like_orders.id > ? and like_orders.status = ?", from_id, 'pending')
end
end
I'm getting the greater than from_id and pending ones.
I made a Join(:likes) But don't know how to Get Not Liked ones from likes table? I've been trying for 6 hours with no luck.
UPDATED: (1 Oct 2015)
Finally I think this is working:
class LikeOrder < ActiveRecord::Base
...
def self.not_likeds(user, from_id)
not_liked = []
pending_form_id(from_id).each do |order|
not_liked << order if order.likes.where('user_id = ?', user.id).count == 0
end
not_liked
end
end
But there might be another way without any block, using where method. can anyone help?
UPDATED: (15 Dec 2015)
I found a better solution:
where("id > ? AND status = ?",from_id, 'pending').where("id not in (SELECT like_order_id from likes WHERE user_id = ?)",user.id).where("user_id != ?",user.id).limit(limit)
I want to get Orders that: their status are pending and the id is
greater that from_id param and are not liked by logged in User.
#app/models/order.rb
class Order < ActiveRecord::Base
def not_liked user, from_id
joins(:likes).where(status: "pending", id > from_id).not(likes: {user_id: user.id})
end
end
This would allow you to call:
#order = Order.not_liked current_user, "5"
Not tested.
Your structure really confused me; why don't you just have a Like model instead of LikeOrder...
#app/models/like.rb
class Like < ActiveRecord::Base
#you could include an "order" attribute here
belongs_to :user
belongs_to :order
validates :user, uniqueness: { scope: :order, message: "Only one Order like per user!" }
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :likes
has_many :favourites, through: :likes, class: "Order", foreign_key: "order_id"
end
#app/models/order.rb
class Order < ActiveRecord::Base
has_many :likes
has_many :fans, through: :likes, class: "User", foreign_key: "user_id"
end
This would allow you to call...
#user = User.find params[:id]
#user.favourites => all the orders they liked
Hey you can try in this way
def self.not_likeds(user, from_id)
joins(:likes).where("like_orders.id > ? and like_orders.status = ? and likes.user_id not in (?)", from_id, 'pending',current_user.id)
end
UPDATE
In this case, I'm guessing that user is current_user so try to do this:
def self.not_likeds(user, from_id)
joins(:user, :likes).where("like_orders.id > ? and like_orders.status = ? and likes.id NOT IN (?)", from_id, 'pending', user.like_ids)
end
the solution for me was:
class LikeOrder < ActiveRecord::Base
...
def self.not_liked(user, from_id=0, limit=20)
where("id > ? AND status = ?",from_id, 'pending').where("id not in (SELECT like_order_id from likes WHERE user_id = ?)",user.id).where("user_id != ?",user.id).limit(limit)
end
end

Get the owner of an unsaved HABTM collection proxy in ActiveRecord?

Lets say I have two ActiveRecord classes:
class Student < ActiveRecord::Base
has_and_belongs_to_many :guardians
end
class Guardian < ActiveRecord::Base
has_and_belongs_to_many :students, inverse_of: :guardian
before_validation :generate_username, on: :create
def generate_username
count = students.count
self.username = 'g'+students.first.admission_no
end
end
And the create function of my GuardiansController:
def create
#student = Student.find(params[:student_id])
#guardian = #student.guardians.build(guardian_params)
flash.now[:success] = 'Guardian was successfully created.' if #guardian.save
respond_with(#guardian)
end
The generate_username function of the Guardian ActiveRecord Class fails because students returns an empty Association CollectionProxy.
How can I get the owner (student) of a Guardian before it is saved??

Ruby Validation When Adding to Database

I am really new in Ruby and I am on the last step to finish my project, when I'm trying to add appointment I have to change if doctor works in that time. I don't know how to do this :(
It is how my db works:
In appointment I have data_wizyty (visit_date), doctor_id and godzina_wizyty(visit_time) - it is in my adding form.
In schedules I have:
dzien_tygodnia(day_of_the_week), poczatek_pracy(start_working), koniec_pracy(end_working) and doctors_workplace_id
In doctors_workplace:
doctor_id, schedule_id, clinic_id
I want to check if doctor is available in any of the clinic in choosen date and time :)
Please help me with this :)
I have already validated if date and time is unique with:
class Appointment < ActiveRecord::Base
validates :doctor_id, uniqueness: { scope: [:data_wizyty, :godzina_wizyty], message: 'Ten termin jest juz zajety!' }
end
I need to check if it is unique and if doctor works.
Appointment:
class Appointment < ActiveRecord::Base
validates :doctor_id, uniqueness: { scope: [:data_wizyty, :godzina_wizyty], message: 'Ten termin jest juz zajety!' }
after_initialize :aInit
after_save :aSave
belongs_to :patient
belongs_to :doctor
belongs_to :schedule
belongs_to :refferal
belongs_to :clinic
has_many :employees
include MultiStepModel
def self.total_steps
3
end
def aInit
#wymaga_Potwierdzenia = true
end
def aSave
if self.refferal_id == nil
#potwierdzona = false
else
#potwierdzona = true
end
if self.wymaga_Potwierdzenia == false
#potwierdzona = true
end
end
end
Schedule:
class Schedule < ActiveRecord::Base
has_many :appointments
belongs_to :clinic
belongs_to :doctors_workplace
def full_schedule
"#{dzien_tygodnia} : #{poczatek_pracy} - #{koniec_pracy}"
end
end
Doctors_workplace:
class DoctorsWorkplace < ActiveRecord::Base
has_many :schedules
belongs_to :doctor
belongs_to :clinic_surgery
end
Now I have something like this :
def check_doctor_available
if Schedule.where(doctor: doctor, dzien_tygodnia: data_wizyty.wday)
.where('poczatek_pracy < ? and koniec_pracy > ?', godzina_wizyty, godzina_wizyty).empty?
self.errors.add(:doctor, message: 'nie pracuje w tym terminie!')
end
It's what I have now:
def check_doctor_available
if DoctorsWorkplace.where(doctor_id: doctor_id) and
Schedule.where(doctors_workplace_id: ????, dzien_tygodnia: data_wizyty.wday)
.where('poczatek_pracy < ? and koniec_pracy > ?', godzina_wizyty, godzina_wizyty).empty?
self.errors.add(:doctor, message: 'nie pracuje w tym terminie!')
end
You can use a custom validation. Create a private method in appointment that checks if the doctor is available at the given date/time.
validate :check_doctor_available
private
def check_doctor_available
#your implementation
end
Take a look at this if you have any doubts what to write in your custom validation method.

Resources