How to read attributes from another model in Rails - activerecord

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?

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 }

Related

Rails scope with nested association

I'm trying to get to grips with rails scopes. I have the simple basics down but I'm trying to create a slightly more complex scope and I'm having some trouble.
class Client
has_many :referrals, through: :submissions
has_one :address
has_many :submissions
end
class Submission
belongs_to :client
belongs_to :user
has_many :referrals, :inverse_of => :submission, dependent: :delete_all
end
class Referral
belongs_to :branch
belongs_to :submission
has_one :client, through: :submission
end
class Address
belongs_to :client
end
I also have users created using devise. I have a custom attribute added to users called city_town.
When a user signs up, they select what city or town they are from and the agency that they work for. When submissions are created, they take nested attributes for client details and address, as well as referral details. referrals take an agency_id that specifies where that referral is going to.
What I'm trying to achieve is to create a scope that will collect all referrals where the referral.client.address.city_town matches the current user's city or town i.e: current_user.city_town and the agency_id of the referral matched the agency_id of the signed in user.
In short, when a user signs in, they can see referrals only for their agency and area.
So far I've got:
class Referral
scope :agency_referrals, -> (id, city_town) { includes(:clients).where(agency_id: id, client.address.city_town => city_town) }
end
but I'm painfully aware that this is far from correct. I get this error:
undefined local variable or method `client' for #<Class:0x00000003200c08>
Any idea where I'm going wrong?
You are receiving this particular error because you are improperly referencing the city_town in your query. client.address.city_town => city_town would imply that the key in this hash is a value nested within an existing "client" variable. This is more correct, and will likely remove the particular error you've just encountered:
By the way, the commenter Pavan was correct, :client should be singular in your .includes() statement. That said, I also expanded it to include the Address table up front, which may have caused additional errors.
# The .includes() parameters have been changed, and quotes have been added to the query.
scope :agency_referrals, -> (id, city_town) { includes(client: :address).where(agency_id: id, 'client.address.city_town' => city_town) }
Also, if you're looking for a non-string method of referencing the proper location (because this is Ruby, and we love our symbols), you could write this:
includes(:clients).where(agency_id: id, client: { address: { city_town: city_town } } => city_town)
I personally find this to be less clean, but it is a more "modern" format.
For further reference, you may wish to review this documentation.
could you try
includes(submission: :clients)

How to query with has_may/belongs_to associations in controller?

I am really confused on how the has_many and belongs_to works within the controller, more specifically how to query the data.
I have a Users modal, and Tasks model, and a User can have many Tasks and Tasks belong to one particular user.
Here are my Model's:
class Task < ActiveRecord::Base
belongs_to :user
validates :title,
presence: true,
length: {minimum: 5, maximum: 50}
validates :description,
presence: true,
length: {minimum: 1, maximum: 140}
end
class User < ActiveRecord::Base
has_many :tasks, dependent: :destroy
has_secure_password
validates :email,
presence: true,
uniqueness: true
end
So in my Tasks controller for example how would I implement the this same action:
def index
# Get all tasks from database
#tasks = Task.all
# how would you achieve the same thing, but only show tasks that belong to a specific user? something like this:
#tasks.users.find(:all)?
end
I have been doing research but I can't seem get a grasp on this. anyways any explanation would help out a lot. thanks guys.
http://guides.rubyonrails.org/active_record_querying.html
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many
First find the User record using find or find_by_id.
user = User.find_by_id(id)
Then call tasks on the user object,which will list all the tasks of that particular user.
list_of_tasks = user.tasks
For proper ways of querying a has_many association, refer to Rails' documentation on eager loading associations (specifically the includes method):
http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations
I'm not sure exactly what kind of information you'd like to pull from your database tables, but here are a couple examples of how you might query all tasks and their associated users.
#tasks_and_users = Task.all.includes(:user)
#tasks_and_users.each do |task|
puts "The user with email '#{task.user.email}' has this task: #{task.title}"
end
Or, if you'd like a list of all users (regardless of whether or not they have any tasks) and their associated tasks, here is an alternative:
#users_and_tasks = User.all.includes(:tasks)
#users_and_tasks.each do |user|
puts "The user with with email '#{user.email}' has the following tasks:"
user.tasks.each do |task|
puts "\t Task: #{task.title}"
end
end

Many to many and one to many association between same models

I am creating a simple Sinatra app, using Sequel for my ORM.
Most of the data revolves around users and events where:
An event can have many users, one of which is the "owner".
Users can have many events, one or many of which they "own".
Here is a simplified version of my schema/model definitions:
class User < Sequel::Model(:users)
many_to_many :events
one_to_one :event
end
class Event < Sequel::Model(:events)
many_to_many :users
many_to_one :user
end
# provides a link between users and events
# e.g. event.users or user.events
# I am unsure how necessary this is :)
db.create_table :events_users do
primay_key :id
foreign_key :event_id, :events
foreign_key :user_id, :users
end
This allows me to get the users attached to an event, or the events that a user is attached to, but I am struggling to express the "ownership" of an event. It seems like the following pseudocode would work:
my_user = User.all.first
owned_events = Event.where(user_id = my_user.user_id)
That leads to two questions:
Does the current way i'm using assocations make sense?
How do I express ownership of an event in terms of Sequel's association model?
Maybe something like this:
class Event
many_to_one :owner, :class=>:User
many_to_many :users
end
class User
one_to_many :owned_events, :class=>:Event, :key=>:owner_id
many_to_many :events
end
You'll need to add owned_id field in events table.
Usage:
user = User.all.first
event = Event.new(:title => 'New Event')
events.add_owner(user)
event.save
another_user = User.create(:name => 'Jack')
event.add_user(another_user)

Rails 3.1 - Binding HABTM from other controller

I have:
class Person < ActiveRecord::Base
has_many :people_phones
has_many :phones, :through => :people_phones
end
I also have:
class Request < ActiveRecord::Base
belongs_to :person
belongs_to :phone
end
Now when someone call with a request I open "requests#new" form, fill in person_id and phone_number and other details and submits them to "requests#create" controller#action.
In the "requests#create", I can do:
#phone = Phone.find_or_create_by_phone_number(params[:phone][:phone_number])
But how can I bind Person with that Phone from this Requests controller?
I mean create a record in people_phones table (if it doesn't exists)?
User.find(person_id).phones << #phone
I don't really know how your app works, but you see the idea.
If you have a request, and you want to "validate" it, you would do
request.person.phones << request.phone
Interesting stuff to know, it's kind of related (I'll try to find where I found that, it was a long time ago)
Steps required for the object to be saved to database:
New
Blog.new(…).save
user.blogs << Blog.new(…)
user.blogs.new(…).save – do not use, no practical use case
Build
Blog.build – not possible
user.blogs.build(…), user.save – both are required to save to DB
Create
Blog.create(…)
user.blogs.create(…)

activerecord validation - validates_associated

I'm unclear on what this method actually does or when to use it.
Lets say I have these models:
Person < ...
# id, name
has_many :phone_numbers
end
PhoneNumber < ...
# id, number
belongs_to :person
validates_length_of :number, :in => 9..12
end
When I create phone numbers for a person like this:
#person = Person.find(1)
#person.phone_numbers.build(:number => "123456")
#person.phone_numbers.build(:number => "12346789012")
#person.save
The save fails because the first number wasn't valid. This is a good thing, to me. But what I don't understand is if its already validating the associated records what is the function validates_associated?
You can do has_many :phone_numbers, validate: false and the validation you're seeing wouldn't happen.
Why use validates_associated then? You might want to do validates_associated :phone_numbers, on: :create and skip validation on update (e.g. if there was already bad data in your db and you didn't want to hassle existing users about it).
There are other scenarios. has_one according to docs is validate: false by default. So you need validates_associated to change that.

Resources