ryanb082
ryanb082

Reputation: 101

How to read attributes from another model in Rails

I still would consider myself new to Rails. I'm implementing a SMS feature in Rails app that reminds clients of their upcoming appointments. My question is, I have the SMS method in my appointment model, but my client model is where the phone attribute is located. How do I call my phone attribute from the appointment model.

Here is my appointment model

class Appointment < ApplicationRecord
  enum status: { confirmed: 0, rescheduled: 1, cancelled: 2}
  belongs_to :user
  belongs_to :client
  validates :start_time, presence: true
  validates :end_time, presence: true

  after_create :reminder

  def reminder
    @twilio_number = ENV['TWILIO_NUMBER']
    account_sid = ENV['TWILIO_ACCOUNT_SID']
    @client = Twilio::REST:Client.new account_sid, ENV['TWILIO_AUTH_TOKEN']
    time_str = ((self.start_time).localtime).strftime("%I:%M%p on %b. %d, %Y")
    reminder = "Hi #{client.name}. Just a reminder that you have an appointment coming up at #{time_str}."
    message = @client.api.account(account_sid).messages.create(
      :from => @twilio_number,
      :to => client.phone_number,
      :body => reminder,
    )
  end

My client model

class Client < ApplicationRecord
  has_many :appointments
  has_many :users, through: :appointments

  scope :clients_by, ->(user) { where(user_id: user.id) }
end

Based on my current associations setup. In the reminder variable couldn't I just call

reminder = "Hi #{client.name}.?

And for

:to => client.phone_number

to access the phone_number attribute?

Upvotes: 0

Views: 477

Answers (1)

AndrewSwerlick
AndrewSwerlick

Reputation: 913

Yes your assumption is correct, you can just call client.<attribute>.

However, be aware of the dreaded SELECT N+1 issue. So let's say you do something like

Appointment.all.each do {|a| a.reminder }

If you have 50 appointments, this results in 51 calls to the database, one call to load all the appointments and then a bunch of calls to load each client one by one.

To avoid this issue, you can make use of includes, eager_loads, or preload which all load the associated data more efficiently than individual queries.

The difference between those three methods is covered very well in this article http://blog.scoutapp.com/articles/2017/01/24/activerecord-includes-vs-joins-vs-preload-vs-eager_load-when-and-where. I've quoted a TL;DR excerpt below.

I'd roughly summarize my approach to these methods like this: If I'm just filtering, use joins. If I'm accessing relationships, start with includes. If includes is slow using two separate queries, I'll use eager_load to force a single query and compare performance.

There are many edge cases when accessing relationships via ActiveRecord. Hopefully this is enough to prevent some of the more basic performance deadends when using joins, includes, preload, and eager_load.

Following the advice of that article, we'd rewrite my example as

Appointment.all.includes(:client).each do {|a| a.reminder }

Upvotes: 1

Related Questions