Joining Unrelated Tables in Rails - sql

Bottom line - I want to join two database tables together that do not "belong to" each other but do have a common field.
After some research on StackOverflow, I found some solutions to similar problems that use SQL directly, like so:
Address.joins('INNER JOIN phones on addresses.person_id = phones.person_id').select("addresses.*, phones.*").limit(1)
When I use the above statement in my rails console, however, it performs the following SQL query:
Address Load (0.3ms) SELECT addresses.*, phones.* FROM `addresses` INNER JOIN phones on addresses.person_id = phones.person_id LIMIT 1
and returns the following data (censored for privacy reasons):
=> #<ActiveRecord::Relation [#<Address id: 0001, created_at: "YYYY-MM-DD hh:mm:ss", updated_at: "YYYY-MM-DD hh:mm:ss", description: nil, line1: "123 Evergreen Terrace", line2: "", line3: "", city: "Springfield", state: "IL", country: "USA", zip: "11111", building_number: "001", person_id: 1, address_type: "Home", primary: false, show: nil, job_id: nil, room_number: "001", source: "HR", effective_date: "YYYY-MM-DD">]>
Not a single field from the Phone table made it into the final record.
Do I have to make these unrelated tables "belong to" each other to make them work? Like -
class Address < ActiveRecord::Base
belongs_to :person
end
class Phone < ActiveRecord::Base
belongs_to :person
belongs_to :address
end
Or can I do what I want without modifying my models' relationships?

The data might not be visible in the query, but the record will respond to those fields that were fetched by the query, meaining if the phone record has a number attribute for example, doing a call to .number will respond with the fetched number
address = Address.joins(....).select('*').first
address.number # => this will print the phone number of the phone record.

Related

Find cards with today date when user receive mails in RoR

Every day I need to send letters to users with today's tasks.
For do this I need to find all users who are allowed to send letters, and among these users to find all cards that have a deadline today. The result is three array elements with a nil value. How is this better done and right?
#users = User.all {|a| a.receive_emails true}
#user_cards = []
#users.each_with_index do |user, index|
#user_cards[index] = user.cards.where(start_date: Date.today).find_each do |card|
#user_cards[index] = card
end
end
My user model:
class Card < ApplicationRecord
belongs_to :user
# also has t.date "start_date"
end
My card model:
class User < ApplicationRecord
has_many :cards, dependent: :destroy
# also has t.boolean "receive_emails", default: false
end
Something like #cards_to_send = Card.joins(:users).where("users.receive_emails = true").where(start_date: Date.today)
Have a look at https://guides.rubyonrails.org/active_record_querying.html#specifying-conditions-on-the-joined-tables for the docs on how to query on a joined table.
You could do this with a SQL join like this
User.joins(:cards).where(receive_emails: true, cards: { start_date: Date.today })
https://guides.rubyonrails.org/active_record_querying.html#joining-tables

select vs distinct vs uniq?

I am confused by Ruby's ActiveRecord uniq method. I am using it to try to get back an array of complete objects, not just a particular field.
In my Padrino app script, which saves newspaper names and scores as Score objects, the uniq method by attribute on an ActiveRecord Relation is not working, and neither is distinct, with or without SQL syntax. Can anyone explain what is going on?
class Score < ActiveRecord::Base
def self.from_today
self.where('created_at > ?', Date.today)
end
end
scores = Score.from_today
scores.class
=> Score::ActiveRecord_Relation
scores.first
=> #<Score id: 123, score: -2.55, source: "Mail", created_at: "2016-08-11 04:29:24", updated_at: "2016-08-11 04:29:24">
scores.map(&:source)
=> ["Mail", "Guardian", "Telegraph", "Independent", "Express", "Mail"]
scores.uniq(:source).count
=> 6
scores.distinct(:source).count
=> 6
scores.select('distinct (SOURCE)').count
=> 5 #AHA, it works!
scores.select(:source).distinct
=> #<ActiveRecord::Relation [#<Score id: nil, source: "Telegraph">, #<Score id: nil, source: "Mail">, #<Score id: nil, source: "Independent">, #<Score id: nil, source: "Express">, #<Score id: nil, source: "Guardian">]>
#oops, no it doesn't
In Rails 5 distinct has no parameter. In Rails 4.2 the parameter is only true or false. When true, return distinct records, when false return none distinct records. uniq is in this case only an alias for distinct
So for Rails 4
scores.select(:source).distinct.count is what you want. This restricts distinct to column source

Rails - Serialize related data

I've got two models:
class Continent < ActiveRecord::Base
has_many :countries
end
class Country < ActiveRecord::Base
belongs_to :continent
end
I created controller like:
class ContinentsController < ApplicationController
def index
#continents = Continent.all
render json: #continents
end
end
and serializer:
class ContitnentSerializer < ActiveModel::Serializer
attributes :name, :countries
end
Here my issue begins. I'd like to serialize only countries with given condition where value comes from HTTP GET params. E.g country inside serializer should be displayed only if population is more than params[:population]. The problem is inside serializer we don't have access to params to examine that.
[
{
name: 'Europe'
countries: [
{
name: 'Italy',
population: 1000000
}
]
},
{
name: 'Africa'
countries: [
]
}
]
I've tried to join table with condition but it seems be not working.
#continents = Continent.all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > #{params[:population]}")
Create a scope and call the scope with param value from controller:
scope :population_more_than, ->(population) {all.joins("LEFT JOIN countries ON countries.continent_id = continents.id AND countries.population > ?", population)}
Now call it from controller instead of Continent.all
Continent.population_more_than(params[:population])
You can try
#continents = Continent.all
#continents.num_population = params[:population]
render json: #continents.to_json(methods: :countries_with_population_gt)
in your Continent model
attr_accessor :num_population
def countries_with_population_gt(num_population=0)
countries.where('population > ?', #num_population)
end
Basically, you need to select only Continents that fall under specific rule. If this is a frequently used filter, then I would go with the Babar's suggestion and create a scope.
If this is a one time selection, then I prefer simply do filtering right there without cluttering up my models with non-frequently used scopes.
Continent.joins(:countries).where("countries.population > :population", population: params[:population])
# Or event shorter
Continent.joins(:countries).where("countries.population > :population", params)

Access attributes from an associated model rails 3

I want to display the ingredients for each recipe on the recipe show view, the ingredients are in a separate model called ingredients but have a belongs_to relationship with the recipe model
So when i call user name for example i use
#recipe.user.name
This is because the attributes are within the recipe model, I have a method in the recipe helper like so
def ingredient_names(ingredients)
if ingredients
ingredient_array = ingredients.map {|ing| ing.ingredient_name}
ingredient_array.join("\n")
end
end
So i thought i could call
#recipe.ingredient_names
but i get an undefined method error..
Then i thought i had to pass the params ingredient_name (name of the column)
#recipe.ingredient_names(:ingredient_name)
but still get undefined method error
and when i do
<%= #recipe.ingredients(:ingredient_name) %>
i get this as the output
[#<Ingredient id: 71, ingredient_name: "Ingredient 1", recipe_id: 56, created_at: "2012-11-29 19:29:25", updated_at: "2012-12-02 16:29:58">, #<Ingredient id: 76, ingredient_name: "ingredient 2", recipe_id: 56, created_at: "2012-12-02 16:29:59", updated_at: "2012-12-02 16:29:59">, #<Ingredient id: 77, ingredient_name: "ingredient 3", recipe_id: 56, created_at: "2012-12-02 16:29:59", updated_at: "2012-12-02 16:29:59">]
so how do i call the attributes for another model when i have the relationship belongs_to and has_many. Simple question i guess but cant figure it out
thanks
Figured out the solution, i needed to access the method first
<%= ingredient_names #recipe.ingredients(:ingredient_name)

Search multiple entries (hash values) in controller

Here's what I'm trying to do:
I've got three tables:
Appointments Users and Shares -
I'd like to assign users to an appointment.
So what I did until now is that I created a table in between appointment and user which
stores the user_id and appointment_id which a user is assigned to.
I'm able to search the shares for a specific appointment in my appointments_controller
with
#shares = Shares.find(:all, :conditions => { :appointment_id => #appointment.id })
After that I get a hash #shares which looks like this:
[#<Shares id: 10, user_id: 3, appointment_id: 1, created_at: "2012-12-04 10:24:16", updated_at: "2012-12-04 10:24:17">, #<Shares id: 12, user_id: 2, appointment_id: 1, created_at: "2012-12-04 10:28:38", updated_at: "2012-12-04 10:28:39">]
What I'd like to do now is a query in the users table to find the usernames that belong to the user_ids i found in the shares hash.
I tried that with something like this:
#shares.each do |s|
#participants = User.all(:conditions => {:id => s.user_id})
end
But it doesn't work because the participants hash gets overwritten every time the loop starts...
Thanks for your help
#shares = Shares.find(:all, :conditions => { :appointment_id => #appointment.id })
#participants = User.all(:conditions => ["id IN (?)", #shares.map{|s| s.user_id}.compact])