Очень странный результат с ассоциацией геокодера has_many и own_to

Я использую Geocoder Gem, он показывает проводное поведение.

У меня есть модель местоположения

class Location < ActiveRecord::Base
has_many :events
geocoded_by :address
after_validation :geocode
def full_address
    "#{postal_code}, #{city}, #{street}"
  end
end

и модель событий

class Event < ActiveRecord::Base
    belongs_to :location
    accepts_nested_attributes_for :location
end

Я хочу найти все события рядом с текущим местоположением пользователя.

Я попытался выполнить следующие шаги, чтобы найти близлежащие места.

nearby_loc = current_user_location.nearby

он возвращает все близлежащие «местоположения» текущего местоположения пользователей

Затем я попытался

nearby_loc.events

но это дает мне ошибку

NoMethodError: undefined method `events' for #<ActiveRecord::Relation::ActiveRecord_Relation_Location:0x0000000958c088>

Помогите мне, пожалуйста ...


person Adt    schedule 17.06.2015    source источник


Ответы (4)


events определяется в местоположении, а nearby даст вам список местоположений, поэтому вам придется перебирать список.

Проще говоря:

all_related_events  = []
nearby_locations.includes(:events).each do |location|
  all_related_events += location.events
end 

Также полезно, если ваши имена переменных более точно отражают то, что они содержат, поэтому используйте nearby_locations вместо nearby_loc.

[ОБНОВИТЬ]

Чтобы свести к минимуму количество запросов, я добавил .includes(:events), который будет получать все события в одном запросе.

person nathanvda    schedule 17.06.2015
comment
выдает ошибку NoMethodError: неопределенный метод +' for nil:NilClass from (irb):11:in block в irb_binding' - person Adt; 17.06.2015
comment
снова ошибка TypeError: неверный тип аргумента Symbol (ожидаемый модуль) - person Adt; 18.06.2015
comment
Да, это должно быть includes очевидно. - person nathanvda; 19.06.2015

Я предпочитаю начинать с запроса с модели, которую хочу получить. Вместо того, чтобы получать «коллекцию из коллекции коллекций», вы можете...

Присоедините Location ко всем событиям, чтобы получить все Event, чьи Location соответствуют определенным условиям. В большинстве случаев область действия — это просто набор условий, которые могут быть merged.

Вот так:

Event.joins(:location)                    # Results in `INNER JOIN`
     .merge(current_user_location.nearby) # Adds in the conditions for locations

Но все не так просто!

Geocoder делает очень сложный select под капотом и добавляет некоторые полезные поля, такие как distance, которые зависят от точки, которая была передана в область видимости. Мы не можем просто так их потерять, верно? Запрос перестанет иметь какой-либо смысл.

Можно сделать INNER JOIN очень странным способом: указав FROM-клаузу для получения данных из двух таблиц (подробнее об этом позже) и указав условие соединения в пункте WHERE. Для этого нам понадобится немного Arel, так что давайте заранее вытащим таблицы:

locations = Location.arel_table
events    = Event   .arel_table # Yeah, call me an indentation maniac

А теперь загвоздка: вместо таблицы locations мы будем использовать результаты подзапроса, сформированного current_user_location.nearby. Как? Мы снабдим from массивом вещей, которые мы хотим использовать:

Event.from([current_user_location.nearby.as('locations'), events])
           # ^ an array, yeah!

Что мы имеем здесь:

select events.* from (geocoder subquery) locations, events

Что теперь? Условие присоединения. Как я уже сказал, поскольку мы делаем странное соединение, мы укажем условие соединения в where. Мы должны.

Event.from([current_user_location.nearby.as('locations'), events])
     .where(location_id: locations[:id])

... и это, вероятно, должно работать нормально. Сделано полностью базой данных по крайней мере.

person D-side    schedule 17.06.2015
comment
@ Адт, ага. Таким образом, область действия Geocoder имеет select... дайте мне немного подумать - person D-side; 17.06.2015
comment
О боже. Похоже, нам нужно присвоить псевдоним locations этому результирующему набору, а затем присоединиться к нему, а не к фактической таблице. Полагаю, ни один индекс не попадет... Вау, это страшно. Я должен получить минимальную настройку Geocoder в Rails, просто чтобы быть уверенным. - person D-side; 17.06.2015
comment
@Adt где-то на грани ошибки и детали реализации, которую мы не должны были затронуть. merge в наши дни используется не слишком широко, и это позор. Это позволяет делать красивые движения. - person D-side; 17.06.2015
comment
@Adt нет, merge хрупкий и его легко разбить. Кстати, на какой базе данных вы работаете? Тип и версия сервера. - person D-side; 17.06.2015
comment
я запускаю postgre и версию gem gem 'pg', '0.18.1' - person Adt; 17.06.2015
comment
@ Не драгоценный камень, сервер. Сама база данных. - person D-side; 17.06.2015
comment
Не знаю, подскажите, как мне это найти? - person Adt; 17.06.2015
comment
@Adt и дикая догадка: попробуйте вставить .select('events.*') в конец моего запроса. Сомневаюсь, что это сработает, но попытка не помешает. - person D-side; 17.06.2015
comment
Версия базы данных @Adt может отличаться на вашей машине разработки и на Heroku. И это не имеет значения, решение, о котором я думал, не сработает. Я пробую другой. - person D-side; 17.06.2015
comment
@Adt теперь есть некоторые серьезные уловки Арела. Проверьте, работает ли он сейчас. - person D-side; 17.06.2015
comment
после запуска Event.from([current_user_location.nearby.as('locations'), events]) в консоли он показывает проводное поведение. запрос возвращает 506 строк (исходно 30 строк в обеих таблицах), а также зависает консоль. - person Adt; 18.06.2015
comment
@Adt, конечно, так как вы забыли where, который должен отфильтровать это. - person D-side; 18.06.2015

В таком случае

nearby_loc = current_user_location.nearby

возвращает список местоположений, а не одно местоположение.

Чтобы просмотреть их и найти события каждого местоположения, вы можете использовать

nearby_events = nearby_loc.map {|loc| loc.events}

Однако это неэффективно с точки зрения общего количества запросов.

person Sploadie    schedule 17.06.2015
comment
Ваше решение работает, но использует много запросов к базе данных. pastebin.com/L4iFn2W1 - person Adt; 17.06.2015
comment
Спасибо, что нашли это! Я добавил возможное решение этого. - person Sploadie; 17.06.2015

NoMethodError: неопределенный метод «события» для ActiveRecord::Relation::ActiveRecord_Relation_Location

Ваш nearby_loc равен ActiveRecord::Relation, поэтому nearby_loc.events приводит к ошибке. Вы должны перебрать nearby_loc, чтобы он заработал.

nearby_loc.each do |n|
n.events
end
person Pavan    schedule 17.06.2015