I want to push dummy content in my database. There is a 1:n relationship concepted in my models.
seeds.rb:
city= City.create([{:ort_name=>"Hagen"}])
person= Person.create([{:name = "root"}])
I try this
city << person
and
person.city_id = city.id
How would you do this?
Your question is a bit vague. It seems like you have a relationship where a city has many people. If so, then you can assign the relationship like this:
city.people << person
or this
person.city_id = city.id
person.save
This method requires save since assigning the city_id to person does not write the change to the database.
You can try these out in your rails console
Related
I am building a Rails 5.2 app.
In this app I got three objects:
User
Id
Firstname
Lastname
Conversation
Id
Entity_type
Entity_subtype
Assignment
Id
User_id
Assignable_id (ID of Conversation)
Assignable_type (Class name of Conversation)
One or more Users are connected to a Conversation through Assignments.
I am trying to create a query that checks if two Users are having a direct message Conversation or I need to create a new one automatically.
I have this code and it "works" (I get no error) but it returns a Conversation even though only one of the Users are involved in a Conversation. But I need it to only return a Conversation if Both Users share the Same Conversation, ie they are talking to each other.
Conversation.where(entity_type: "conversation", entity_subtype: "direct").joins(:assignments).where(:assignments => {:user_id => ["cdd3c6be-ac78-46f2-a7ae-7f2299b6fedb", "32117e53-9b2f-49c6-8cc8-3a9eb9003a2e"] })
One way to do this is by using GROUP and using HAVING to set a condition on the group:
class Conversion < ActiveRecord::Base
def self.between(*users)
joins(:assignments)
.group(:id)
.where(assignments: { user_id: users })
.having(Assignment.arel_table[:id].count.eq(users.length))
end
end
The advantage is that this approach can be used with any number of users.
If you change out .eq to .gteq you can get conversions that include the two users but isn't a private conversation. On Postgres you can use Assignment.arel_table[Arel.star] as an optimization.
Join assignments twice and in resulting rows, pick a row with assignment user ids match.
user_ids = ["cdd3c6be-ac78-46f2-a7ae-7f2299b6fedb", "32117e53-9b2f-49c6-8cc8-3a9eb9003a2e"]
Conversation
.where(entity_type: "conversation", entity_subtype: "direct")
.joins("LEFT JOIN assignments as1 ON assignments.assignable_id = conversations.id AND assignments.assignable_type = 'Conversation'")
.joins("LEFT JOIN assignments as2 ON assignments.assignable_id = conversations.id AND assignments.assignable_type = 'Conversation'")
.where('as1.user_id = as2.user_id')
.where('as1.user_id = ? AND as2.user_id = ?', user_ids[0], user_ids[1])
Will give you list of convos, both are involved
I have the following two models in Rails application
class Students < ApplicationRecord
has_many :courses
attr_accessor :name
end
class Courses < ApplicationRecord
has_many :students
attr_accessor :name, :course_id
end
I would like to get a a list of all courses shared for each student who has been in the same class as a selected student in an efficient manner.
Given the following students:
Jerry's courses ["math", "english", "spanish"],
Bob's courses ["math", "english", "french"],
Frank's courses ["math", "basketweaving"]
Harry's courses ["basketweaving"]
If the selected student was Jerry, I would like the following object returned
{ Bob: ["math", "english"], Frank: ["math"] }
I know this will be an expensive operation, but my gut tells me there's a better way than what I'm doing. Here's what I've tried:
# student with id:1 is selected student
courses = Student.find(1).courses
students_with_shared_classes = {}
courses.each do |course|
students_in_course = Course.find(course.id).students
students_in_course.each do |s|
if students_with_shared_classes.key?(s.name)
students_with_shared_classes[s.name].append(course.name)
else
students_with_shared_classes[s.name] = [course.name]
end
end
end
Are there any ActiveRecord or SQL tricks for a situation like this?
I think you're looking to do something like this:
student_id = 1
courses = Student.find(student_id).courses
other_students = Student
.join(:courses)
.eager_load(:courses)
.where(courses: courses)
.not.where(id: student_id)
This would give a collection of other students that took courses with only two db queries, and then you'd need to narrow down to the collection you're trying to create:
course_names = courses.map(&:name)
other_students.each_with_object({}) do |other_student, collection|
course_names = other_student.courses.map(&:name)
collection[other_student.name] = course_names.select { |course_name| course_names.include?(course_name) }
end
The above would build out collection where the key is the student names, and the values are the array of courses that match what student_id took.
Provided you setup a join model (as required unless you use has_and_belongs_to_many) you could query it directly and use an array aggregate:
# given the student 'jerry'
enrollments = Enrollment.joins(:course)
.joins(:student)
.select(
'students.name AS student_name',
'array_agg(courses.name) AS course_names'
)
.where(course_id: jerry.courses)
.where.not(student_id: jerry.id)
.group(:student_id)
array_agg is a Postgres specific function. On MySQL and I belive Oracle you can use JSON_ARRAYAGG to the same end. SQLite only has group_concat which returns a comma separated string.
If you want to get a hash from there you can do:
enrollments.each_with_object({}) do |e, hash|
hash[e.student_name] = e.course_names
end
This option is not as database independent as Gavin Millers excellent answer but does all the work on the database side so that you don't have to iterate through the records in ruby and sort out the courses that they don't have in common.
My title might be confusing, I wasn't sure what to write.
In rails I understand how to fetch Many Objects for One parent object
#first_user = User.first
#first_user_posts = #first_user.posts
But how can I fetch Many Objects for Many parent objects and select its attributes in one query?. I am trying to do something like that:
#many_posts = Post.all
#posts_by_user_gender = #many_posts.joins(:user).map(&:gender)
hoping it would give me an array that could look something like this:
#posts_by_user_gender => ["male", nil, "female", nil]
#I know I can do this map technique if I fetch it directly from the User model
# User.all.map(&:gender),
# but I want to start with those that posted in a specific category
# Post.where(:category_id => 1)
and then to count the males I could use the Ruby Array method .count
#males_count_who_posted = #posts_by_user_gender.count("male")
=> 1
I could always do 3 separate queries
#males_count_who_posted = #many_posts.select(:user_id).joins(:user)
.where("gender = ?", "male").count
#females_count_who_posted = ...
but I find that extremely inefficient, especially if I do the same for something like "industry" where you could have more than 3 options.
you can join model via SQL syntax
#posts_by_user_gender = #many_posts.joins("LEFT JOIN Users where users.id=posts.user_id").joins("LEFT JOIN Genders where genders.id=user.gender_id")
I have the following classes and relationships
City has_many Cinemas
Cinemas has_many Movies
Movies has_many Ratings
Movies Has_many Genres through GenreMovie
and I want to test queries like
* Show me the all movies in NewYork
* Show me the all movies in NewYork order by the rating
* Show me the all movies in NewYork order by length_of_movie, in genre "Action"
* show me all movies in Cinema "X" order by rating, that are in Genre "SciFi"
Currently the way I am doing as below, using factory girl, and chaining a bunch of models together to have data to check against,
city = create(:city)
cinema = create(:cinema, city: city)
5.times do
movie = create(:movie, cinema: cinema, tags: ["sci fi", "action"]
3.times do
create(:rating, score: 2.3, movie: movie)
end
end
and repeating that 3-4 to generate enough data to query against but it seems so clunky.
Is there a better way ?
I normally test this using a very "minimalistic" approach:
e.g. for your first case I would create two movies, one in NY, and one outside. Your method should only return the one in NY
For the second, create three movies, both in NY, with different rating. Create them in a not logic way, so that, no matter what, they will be sorted. Check whether your method returns them in the right order
Similar for the other cases.
I would not just create 5x3 movies. Makes no sense, and only costs time...
There are several factory_girl constructs you could use to clean these up. create_list will create an array of objects, cleaning up your x.times do blocks. A with_ratings trait on your movie factory could allow you to opt in to having ratings automatically created when you create a movie (via FactoryGirl callbacks). You could even have that use a transient attribute in order to control the number and rating. So your result could look something like this:
cinema = create(:cinema)
movies = create_list(
:movie,
5,
:with_ratings,
cinema: cinema,
tags: [...],
ratings_count: 3,
ratings_value: 2.3
)
If you need access to the city, you can get it via cinema.city.
See:
transient attributes
traits
I have 3 models (Genre, Mood, Tempo) each with a has_and_belongs_to_many association with another model (Track). The user can upload an Excel file with info for each track and I want to be able to create the respective records and add the association all at once in my update method.
I'm reading each row from the spreadsheet and creating arrays holding the genres, moods, and tempos that are to be associated with each track, but I'm not sure how to correctly create the association.
Something like:
1.upto #worksheet.last_row_index do |index|
row = #worksheet.row(index)
genres = row[6].split(", ")
genres.each do |g|
Genre.find_or_create_by_name(g) // not sure what to do here exactly
end
end
The Track ID is row[0].
I need to create each Genre if it doesn't exist, and associate that track with each. If the genre does exist, then I just need to create the association. Same for moods and tempos.
Thanks for any assistance.
This seems to do it.
1.upto #worksheet.last_row_index do |index|
row = #worksheet.row(index)
track = Track.find(row[0])
genres = row[6].split(", ")
genres.each do |g|
track.genres << Genre.find_or_create_by_name(g)
end
end