Basic Rails Association - ruby-on-rails

This is a totally a beginner question. I'm embarrassed to be asking it, but here goes.
We have two models: Person and Order.
Person has attributes :first_name, :last_name, :age. Order has one attribute, :total.
Person has_many :orders, and Order belongs_to :person.
Let's assume that some data has been entered for both models.
Now, we play test this relationship in console:
p = Person.first
o = Order.new(total: 100)
o.person = p # this is equivalent to: o.person_id = p.id, yes?
o.save
p.orders
My questions stem from line 3 and line 5.
Question 1: Why do we have to say o.person instead of o in line 3?
Question 2: Why are we saying p.orders in line 5?
Question 3: What does this, o.person_id = p.id, mean exactly? I'm assuming it's associating the tables with each other?
Let me know if this question is unclear.
Thank you for your help!

Question 1: Why do we have to say "o.person instead of o" in line 3?
Order belongs to a Person, so on that line you specify the exact person who owns that order o by typing o.person = p. o = p doesn't make any sense.
Question 2: And why are we saying "p.orders" in line 5?
Because each Person has many orders, so you can get them by typing p.orders
Question 3: Also, what does this "o.person_id = p.id" mean exactly? I'm assuming it's associating the tables with each other?
Yes, this sets the owner of the order.
Ah, I see additional question:
o.person = p (this is equivalent to: o.person_id = p.id, yes?)
Not always, but in most cases. Say, for polymorphic associations it will not only set id, but also a type.

Question 1: Why do we have to say "o.person instead of o" in line 3?
When you declared that an Order object belongs_to :person, rails created a column in the Orders table called person_id. In the Orders table, the column names are the attributes of an Order object, and you refer to the attributes of an Order object using dot notation, e.g. o.total.
As a convenience, rails lets you assign the whole Person object to an Order attribute named person, then rails extracts the Person id, and rails inserts the id into the person_id column in the Orders table.
Your question is sort of like asking, why you have to write:
o.total = 10
instead of
o = 10
The last line does not tell rails what column in the Orders table that the value should go in.
A table is just a grid of column names and values:
Orders:
id total person_id timestamp1 timestamp2
1 10 1 1234567 4567890
2 30 3 12342134 1324123423
3 20 1 1341234324 12341342344
Then if you write:
o = Order.find(2)
Then o will be assigned an Order object whose values for total, person_id, timestamp1, and timestap2, will be the values in the row where the id is equal to 2.
Next, if you write:
o = 10
What does that mean? Does it mean that all columns for o's row should be set to the value 10? Set the first column to 10? Set the last column to 10? Isn't it much clearer to write o.person = 10?
Question 2: And why are we saying "p.orders" in line 5?
That retrieves all the orders associated with a Person object--remember you declared that a Person object has_many Orders. Once again, that is a convenience provided by Rails--not declaring the associations would force you to write:
target_person_id = 1
#orders = Order.where(person_id: target_person_id)
Question 3: Also, what does this "o.person_id = p.id" mean exactly?
I'm assuming it's associating the tables with each other?
p is a Person object, e.g. one of these rows:
People:
id first last middle order_id timestamp1 timestamp2
1 Tom Thumb T 1 4567890 1234456
2 Wizard Id of 3 1324123423 123434
3 Tom Thumb T 2 2134234 1234234
If p is a Person object created from the the last row of values, then p.id is equal to 3, which means that the line:
o.person_id = p.id
is equivalent to:
o.person_id = 3
Next, o is an Order object, and the Orders table has a column named person_id which was created when you declared: belongs_to: person, and the line:
o.person_id = 3
instructs rails to insert 3 for the value of o's person_id column. If o's id is 1, then you get this:
Orders:
id total person_id timestamp1 timestamp2
=> 1 10 3 <= 1234567 4567890
2 30 3 12342134 1324123423
3 20 1 1341234324 12341342344

Related

Calculate formula through unique rails associations

Say that I have these tables/associations :
Product has_many :keywords, :through => :product_keywords
Keyword has_many :products, :through => :product_keywords
And the ProductKeyword table is :
=> ProductKeyword(id: integer, keyword_position: integer, product_id: integer, keyword_id: integer)
Let's picture the prk variable as a list of products. Each product can have many keywords and keyword is defined as :
=> Keyword(id: integer, phrase: text)
Multiple keywords could contain the same phrases in terms of the text of the Keyword, but they are different database entries.
What I want to do is create a table with all the keywords of all the products and for each keyword calculate the sum of all those keywords that had a keyword_position < 10.
So if 5 products have the same keyword phrase(say "beach") and thus 5 different entries and their respective ProductKeyword keyword_positions are [1, 6, 11, 13, 3], I want that keyword to return a unique entry associated with the sum of less than 10 keyword_positions, which in this case would be 3.
I have tried a few different things, but end up confusing myself. What is the proper way to do this ?
I believe the below code should produce the data you're looking for:
Keyword
.joins(:product_keywords)
.where(ProductKeyword.arel_table[:keyword_position].lt(10))
.group(Keyword.primary_key)
.select(
Keyword.arel_table[Arel.star],
Arel.star.count.as('product_count')
)
This will run the following SQL query (syntax may vary depending on your database):
SELECT "keywords".*, COUNT(*) AS product_count
FROM "keywords"
INNER JOIN "product_keywords" ON "product_keywords"."keyword_id" = "keywords"."id"
WHERE "product_keywords"."keyword_position" < 10
GROUP BY "keywords"."id"
That will return a list of Keyword records; you can run .product_count on each of these records to determine how many associated ProductKeyword records there are with a keyword_position value of less than 10.
You could then create a table to hold the data produced by the above code.
If you wanted to determine the count for a specific Keyword record without running that whole query, the following code should produce that count:
my_keyword.product_keywords.where(ProductKeyword.arel_table[:keyword_position].lt(10)).count

How to get the top 5 per enum of a model in rails?

Let's say I have a model named post, which has an enum named post_type which can either be
admin, public or user
#app/models/post.rb
class Post < ApplicationRecord
enum post_type: [ :admin, :public, :user ]
end
How can I select 5 last created posts from each category?
I can't think of any other solution than this:
PER_GROUP = 5
admin_posts = Post.admin.order(created_at: :desc).limit(PER_GROUP)
user_posts = Post.user.order(created_at: :desc).limit(PER_GROUP)
public_posts = Post.public.order(created_at: :desc).limit(PER_GROUP)
Is there any way I could fetch all the rows in the required manner from just a single query to the database.
STACK
RAILS : 6
PostgresSQL: 9.4
I am not sure how to translate into RAILS, but it is straight forward Postgres query. You use the row_number window function in a sub-select then keep only rows with row_number less than or equal 5 on the outer select.
select *
from (select post_txt
, posted_type
, row_number() over (partition by posted_type) rn
from enum_table
) pt
where rn <= 5
order by posted_type;
One thing to look out for is the sorting on an enum. Doing so gives results in order of the definition, not a "natural order" (alphanumeric in this case). See example here.
Thanks to #Belayer i was able to come up with a solution.
PER_GROUP = 5
sub_query = Post.select('*', 'row_number() over (partition by "posts"."post_type" ORDER BY posts.created_at DESC ) rn').to_sql
#posts = Post.from("(#{sub_query}) inner_query")
.where('inner_query.rn <= ?', PER_GROUP')
.order(:post_type, created_at: :desc)
.group_by(&:post_type)
Since i am only loading 5 records across just a few different types group_by will work just fine for me.

RAILS: How to select fields from associated table with grouping and create a hash from result?

I would like to create an active record query and store the result in a hash which includes summary information from associated tables as follows.
Here is the tables and associations:
Post belongs_to: category
Category has_many: posts
Here I would like to count the # of posts in each category and create a summary table as follows (with the SQL query for the desired table):
select c.name, count(p.id) from posts a left join categories c on p.category_id = c.id where p.status = 'Approved' group by (c.name) order by (c.name);
Category | count
---------------+-------
Basketball | 2
Football | 3
Hockey | 4
(3 rows)
Lastly I would like to store the result in a hash as follows:
summary_hash = { 'Basketball' => 2, 'Football' => 3, 'Hockey' => 4 }
I will appreciate if you can guide me how to write the active record query and store the result in the hash.
Try
Post.where(status: 'Approved').joins(:category).
select("categories.name").group("categories.name").count

ActiveRecord - Getting the sum of user score

I have an "ITEMS" database made of; ITEM_ID, OWNER_ID and VALUE.
An owner can own unlimited items. An item can only have one owner.
And I have an "OWNER" database made of; ID, NAME
I want to find the NAMES of top 10 RICHEST (wealthier) people. How can I do that?
First, I need to sum the values of an owner_id; than compare that with others?
Here is what you can do :
Item.group(:owner_id) # grouping Items by owner id
.select("SUM(value) as sum") # summing values of each group
.order("sum DESC") # ordering resulting records by the sum value
.limit(10) # giving the top 10 records
It is kinda long solution but it worked for me:
toplist = []
all_owners = Owner.all
all_owners.each do |owner|
name = Owner.find(owner).name
owner_value = Item.where(owner_id: owner).sum(:value)
toplist << [owner_value,name]
end
#top10 = toplist.sort.last(10).reverse

Access the contents from third table to has_many_and_belongs_to association in rails

I have semester and subject model and they have has_many_and_belongs_to association. semester table has only semester_name column and subject table has only subject_name column. Third table name semesters_subjects and it has subject_id and semester_id column.
If my semesters_subjects has following data in the table
subject_id semester_id column
1 2
1 3
1 7
2 4
2 3
that is one subject can exist in many semester. Now I want to find the semester_id where subject_id = 1 or if I want to find subject_id where semester_id = 3
In rails how can I do this?
It's many to many relation ship.
#semester = Semester.find(3)
#semester.subjects # will return all semester subjects you can add
# where clause or pluck id as per your need
# similarly for subject
#subject = Subject.find(1)
#subject.semesters #will return subject semesters

Resources