How to query with has_may/belongs_to associations in controller? - ruby

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

Related

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?
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 }

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)

Attempting to create a database item using the has_one relationship, no exceptions, but still no item

Models:
A User has_one Ucellar
A Ucellar belongs_to User
I have confirmed from multiple sources that these are set up correctly. For posterity, here is the top portion of those two models.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
This is actually the entire Ucellar model.
class Ucellar < ActiveRecord::Base
belongs_to :user
end
Ucellar has a column called user_id, which I know is necessary. The part of my application that creates a user uses the method create_with_oath. Below is the entire User class. Note the second line of the create method.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
def create
#user = User.new(user_params)
#ucellar = #user.create_ucellar
end
def add_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def self.create_with_omniauth(auth)
user = User.create({:name => auth["info"]["name"], :email => auth["info"]["email"]})
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
EDIT:
Forgot to summarize the symptoms. On create, the user is in the db, with no exceptions thrown, and nothing to signify that anything went wrong. However, the related ucellar is never created. Per the documentation Here, the create method should create AND save the related ucellar.
It should create ucellar too.
Try to get the error messages after the creation by calling:
raise #user.errors.full_messages.to_sentence.inspect
I'm not sure why this wasn't working, but I ended up just moving this code out of the create action of the user controller, and putting it directly after an action that was creating a user. It solved my issue though. Thanks everyone for your help!

Many-to-Many Uniqueness Constraint Test Not Working

I have a many-to-many relationship with a join table in my Rails application. I'm using the has_many :through idiom in my models. To keep things simple, lets call my first class Student, my second class Course, and the join table class Enrollment (which contains fields student_id and course_id). I want to make sure that a given Student is associated with a given Course at most once (i.e. the {student_id, course_id} tuple should be unique in the enrollment table).
So I have a migration a that enforces this uniqueness.
def change
add_index :enrollments, [:student_id, :course_id], :unique => true
end
In addition my model classes are defined as such:
class Student < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollment
end
class Course < ActiveRecord::Base
has_many :enrollments
has_many :students, :through => :enrollment
end
class Enrollment < ActiveRecord::Base
belongs_to :student
belongs_to :course
validates :student, :presence => true
validates :course, :presence => true
validates :student_id, :uniqueness => {:scope => :course_id}
end
In a rails console, I can do the following:
student = Student.first
course = Course.first
student.courses << course
#... succeeds
student.courses << course
#... appropriately fails and raises an ActiveRecord::RecordInvalid exception
In my RSpec test, I do the exact same thing and I get no exception with the following code:
#student.courses << #course
expect { #student.courses << #course }.to raise_error(ActiveRecord::RecordInvalid)
And so my test fails and reports:
expected ActiveRecord::RecordInvalid but nothing was raised
What's going on here? What could I be doing wrong? How do I fix it?
Rails uses model level validation, if you want strict checking for uniquiness you need to use database level - foreign keys for example. But in this case you need to catch exceptions from database connector.
This is strange because in my code (very similar to your) validation for unique raises exception.
There's a couple of things here that could be happening:
#courses has changed between uses.
#student has changed between uses.
By using let you'll protect these values from changing between expectations.
let(:course) { Course.first }
let(:student) { Student.first }
subject{ student.courses << course << course }
it { should raise_error(ActiveRecord::RecordInvalid) }
Or, there could just be something wrong with your code :)

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