Rails Объединение и включение столбцов из таблицы объединений
Я не понимаю, как получить столбцы, которые я хочу от рельсов. У меня две модели - Пользователь и профиль. Пользователь: has_many Профиль (потому что пользователи могут вернуться к более ранней версии своего профиля):
> DESCRIBE users;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| username | varchar(255) | NO | UNI | NULL | |
| password | varchar(255) | NO | | NULL | |
| last_login | datetime | YES | | NULL | |
+----------------+--------------+------+-----+---------+----------------+
> DESCRIBE profiles;
+----------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user_id | int(11) | NO | MUL | NULL | |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
| . . . . . . |
| . . . . . . |
| . . . . . . |
+----------------+--------------+------+-----+---------+----------------+
В SQL я могу запустить запрос:
> SELECT * FROM profiles JOIN users ON profiles.user_id = users.id LIMIT 1;
+----+-----------+----------+---------------------+---------+---------------+-----+
| id | username | password | last_login | user_id | first_name | ... |
+----+-----------+----------+---------------------+---------+---------------+-----+
| 1 | john | ****** | 2010-12-30 18:04:28 | 1 | John | ... |
+----+-----------+----------+---------------------+---------+---------------+-----+
Посмотрите, как я получаю все столбцы для BOTH tables JOINED вместе? Однако, когда я запускаю этот же запрос в Rails, я не получаю все столбцы, которые я хочу - я получаю только те из профиля:
# in rails console
>> p = Profile.joins(:user).limit(1)
>> [#<Profile ...>]
>> p.first_name
>> NoMethodError: undefined method `first_name' for #<ActiveRecord::Relation:0x102b521d0> from /Library/Ruby/Gems/1.8/gems/activerecord-3.0.1/lib/active_record/relation.rb:373:in `method_missing' from (irb):8
# I do NOT want to do this (AKA I do NOT want to use "includes")
>> p.user
>> NoMethodError: undefined method `user' for #<ActiveRecord::Relation:0x102b521d0> from /Library/Ruby/Gems/1.8/gems/activerecord-3.0.1/lib/active_record/relation.rb:373:in method_missing' from (irb):9
Я хочу (эффективно) вернуть объект, который имеет все свойства профиля и пользователя вместе. Я не хочу: включать пользователя, потому что это не имеет смысла. Пользователь всегда должен быть частью самого последнего профиля, как если бы они были полями в модели профиля. Как это сделать?
Я думаю, что проблема связана с тем, что модель профиля не имеет атрибутов для пользователя...
Ответы
Ответ 1
Я не думаю, что вы можете загружать пользователей и профили с помощью объединения в Rails. Я думаю, что в ранних версиях Rails (< 2.1) загрузка связанных моделей была выполнена с помощью объединений, но она не была эффективной. Здесь у вас есть некоторые объяснения и ссылки на другие материалы.
Итак, даже если вы объясните, что хотите присоединиться к нему, Rails не будет сопоставлять его с соответствующими моделями. Поэтому, если вы скажете Profile.whatever_here
, он всегда будет отображаться в объект Profile
.
Если вы все еще хотите делать то, о чем говорите, вы можете сами вызвать собственный запрос sql и результаты процесса:
p = ActiveRecord::Base.connection.execute("SELECT * FROM profiles JOIN users ON profiles.user_id = users.id LIMIT 1")
и получайте результаты по строкам с помощью:
p.fetch_row
Он уже будет преобразован в массив.
Ваши ошибки вызваны тем, что вы вызываете метод first_name
и user
на объект AciveRecord::Relation
, и он хранит массив объектов Profile
, а не один объект. Так
p = Profile.joins(:user).limit(1)
p[0].first_name
shoud work.
Лучший способ получить только одну запись - вызвать:
p = Profile.joins(:user).first
p.first_name
p.user
Но когда вы вызываете p.user
, он будет запрашивать базу данных. Чтобы избежать этого, вы можете использовать include
, но если вы загружаете только один объект профиля, это бесполезно. Это будет иметь значение, если вы загружаете много профилей за раз и хотите вставлять таблицу пользователей.
Ответ 2
Используйте select(), чтобы назвать нужные столбцы. По крайней мере, это работает в Rails 3.0.9.
Фон: мое приложение имеет первичную таблицу с именем: rights. Я хотел уметь приписывать тег и цвет заданной правильной записи, чтобы я мог легко выбрать ее из списка индексов. Это не полностью соответствует изображению Rails связанных записей; most: права никогда не будут помечены, а теги полностью произвольны (ввод пользователя через тег/редактирование).
Я мог бы попробовать дублировать данные тега в правой записи, но это нарушает нормальную форму. Или я мог бы попробовать выполнить запрос: теги для каждого: правая запись, но это очень неэффективный подход. Я хочу присоединиться к таблицам.
Консоль MySQL показывает:
mysql> describe rights;
+------------+---------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
...
| Tagid | int(11) | YES | | NULL | |
+------------+---------------+------+-----+---------+----------------+
mysql> describe tags;
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| TagName | varchar(255) | YES | | NULL | |
| TagColor | varchar(255) | YES | | NULL | |
| created_at | datetime | YES | | NULL | |
| updated_at | datetime | YES | | NULL | |
+------------+--------------+------+-----+---------+----------------+
Я собираюсь использовать TagName и TagColor в views/rights/index.html.erb, поэтому я хочу, чтобы контроллер прав включал эти столбцы в объект @rights, который он передает в представление. Поскольку не все: right имеет a: tag, я хочу использовать внешнее соединение:
@rights = Right.joins("LEFT OUTER JOIN tags ON rights.Tagid = tags.id")
Но, как выяснилось всеми, это само по себе не работает: ссылка блока на TagName вызывает ошибку сервера. Однако, если я добавлю выделение в конце, все будет хорошо:
@rights = Right.joins("LEFT OUTER JOIN tags ON rights.Tagid = tags.id").select("rights.*,tags.TagName as TagName,tags.TagColor as TagColor")
Примечание добавлено 6/7/13: предложение select не требует псевдонимов - это тоже работает:
.select("rights.*,tags.TagName,tags.TagColor")
Теперь я могу ссылаться на TagName и TagColor на мой взгляд:
<% @rights.each do |right| %>
<tr ALIGN=Left <%=
# color background if this is tagged
" BGCOLOR=#{right.TagColor}" if right.TagColor
%> > ...
<% end %>
Ответ 3
Попробуйте использовать select("*").joins(:table)
В этом случае вы должны ввести:
User.select("*").joins(:profile)
Надеюсь, что это сработает для вас.
Ответ 4
После прочтения этих советов я получил, что все соединения загружаются в один запрос, читая 3 способа сделать загрузку (предварительную загрузку) в Rails 3 и 4.
Я использую Rails 4, и это помогло мне:
refs = Referral.joins(:job)
.joins(:referee)
.joins(:referrer)
.where("jobs.poster_id= ?", user.contact_id)
.order(created_at: :desc)
.eager_load(:job, :referee, :referrer)
Вот мои другие попытки.
#first attempt
#refs = Referral.joins(:job)
# .where("jobs.poster_id= ?", user.contact_id)
# .select("referrals.*, jobs.*")
# works, but each column needs to be explicitly referenced to be used later.
# also there are conflicts for columns with the same name like id
#second attempt
#refs = ActiveRecord::Base.connection.exec_query("SELECT jobs.id AS job_id, jobs.*, referrals.id as referral_id, referrals.* FROM referrals INNER JOIN jobs ON job_id = referrals.job_id WHERE (jobs.poster_id=#{user.contact_id});")
# this worked OK, but returned back a funky object, plus the column name
# conflict from the previous method remains an issue.
#third attempt using a view + rails_db_views
#refs = JobReferral.where(:poster_id => user.contact_id)
# this worked well. Unfortunately I couldn't use the SQL statement from above
# instead of jobs.* I had to explicitly alias and name each column.
# Additionally it brought back a ton of duplicate data that I was putting
# into an array when really it is nice to work with ActiveRecord objects.
#eager_load
#refs = Referral.joins(:job)
# .where("jobs.poster_id= ?", user.contact_id)
# .eager_load(:job)
# this was my base attempt that worked before I added in two more joins :)
Ответ 5
Я столкнулся с этой проблемой, создав VIEW в базе данных, которая является объединением, а затем ссылается на это, как если бы это была нормальная таблица ActiveRecord в коде. Это нормально для получения данных из базы данных, но если вам нужно его обновить, вам нужно будет вернуться к базовым классам, которые представляют "реальные" таблицы. Я нашел этот метод удобным при создании отчетов, которые используют таблицы biggish - вы можете получить данные из всего одного удара. Я удивлен, что это, похоже, не встроено в ActiveRecord, кажется мне очевидным!
Итак, для вас:
IN SQL:
CREATE VIEW User_Profiles
AS
SELECT P.*, U.first_name
FROM Users U
inner join Profiles P on U.id=P.user_id
В файле моделей RUBY:
class UserProfile < ActiveRecord::Base
self.primary_key = :id
#same dependencies as profiles
end
** СОВЕТ... Я всегда забываю установить владельца представления (я использую postgres), поэтому он сразу же сбрасывается с большим количеством проклятий и самообвинения.