Подтвердить что ты не робот

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

Я хочу (эффективно) вернуть объект, который имеет все свойства профиля и пользователя вместе. Я не хочу: включать пользователя, потому что это не имеет смысла. Пользователь всегда должен быть частью самого последнего профиля, как если бы они были полями в модели профиля. Как это сделать?

Я думаю, что проблема связана с тем, что модель профиля не имеет атрибутов для пользователя...

4b9b3361

Ответ 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), поэтому он сразу же сбрасывается с большим количеством проклятий и самообвинения.