Соединения Rails и включают столбцы из таблицы соединений - PullRequest
25 голосов
/ 31 декабря 2010

Я не понимаю, как получить столбцы, которые я хочу от рельсов.У меня есть две модели - пользователь и профиль.Пользователь: has_many Profile (поскольку пользователи могут вернуться к более ранней версии своего профиля):

> 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          | ... |
+----+-----------+----------+---------------------+---------+---------------+-----+

Посмотрите, как я получаю все столбцы для ОБОИХ таблиц, СОЕДИНЕННЫХ вместе?Однако, когда я запускаю этот же запрос в Rails, я не получаю все нужные мне столбцы - я получаю только те из Profile:

# 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

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

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

Ответы [ 5 ]

17 голосов
/ 05 июня 2013

Используйте select (), чтобы назвать нужные столбцы.По крайней мере, это работает в Rails 3.0.9.

Справочная информация: у моего приложения есть основная таблица с именем: rights.Я хотел, чтобы можно было присвоить тег и цвет данной правильной записи, чтобы я мог легко выбрать ее из списка индексов.Это не совсем соответствует картине Rails связанных записей;Большинство: права никогда не будут помечены, и теги являются абсолютно произвольными (пользовательский ввод через тег / редактирование).

Я мог бы попытаться дублировать данные тега в записи: right, но это нарушает нормальную форму.Или я мог бы попробовать запросить теги для каждой записи: right, но это крайне неэффективный подход.Я хочу иметь возможность присоединиться к таблицам.

Консоль 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 есть тег:, я хочу использовать внешнее объединение:

@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 %>
10 голосов
/ 31 декабря 2010

Я не думаю, что вы можете загружать пользователей и профили с помощью объединения в Rails.Я думаю, что в более ранних версиях Rails (<2.1) загрузка связанных моделей выполнялась с помощью объединений, но это было неэффективно.<a href="/915100/rails-vklychit-protiv-soedineniya"> Здесь у вас есть некоторые пояснения и ссылки на другие материалы.

Так что даже если вы объясните, что хотите присоединиться к нему, 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

громкая работа.

Лучший способ получить только одну запись - это позвонить:

p = Profile.joins(:user).first
p.first_name
p.user

Но когда вы звоните p.user, он запросит базу данных,Чтобы избежать этого, вы можете использовать include, но если вы загружаете только один объект профиля, это бесполезно.Это будет иметь значение, если вы загружаете много профилей одновременно и хотите включить таблицу пользователей.

4 голосов
/ 12 мая 2016

Попробуйте использовать select("*").joins(:table)

В этом случае вы должны набрать:

User.select("*").joins(:profile)

Надеюсь, что это работает для вас.

3 голосов
/ 02 августа 2016

После прочтения этих советов я получил возможность загружать все объединения в один запрос, прочитав 3 способа быстрой загрузки (предварительной загрузки) в Rails 3 & 4 .

I'mиспользуя 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 :)
2 голосов
/ 16 ноября 2014

Я обошел эту проблему, создав в базе данных VIEW, который является соединением, и затем сослался на него, как если бы это была обычная таблица ActiveRecord в коде.Это хорошо для извлечения данных из базы данных, но если вам нужно обновить их, вам нужно вернуться к базовым классам, которые представляют «реальные» таблицы.Я обнаружил, что этот метод удобен при составлении отчетов, в которых используются большие таблицы - вы можете получить данные одним ударом.Я удивлен, что это, кажется, не встроено в 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), поэтому он сразу взрывается с большим количеством проклятий и самообвинения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...