Как передать строку в параметр has_many: finder_sql? - PullRequest
5 голосов
/ 30 ноября 2008

В моем приложении пользователь has_many tickets. К сожалению, таблица заявок не имеет user_id: она имеет user_login (это устаревшая база данных). Я собираюсь изменить это когда-нибудь, но сейчас это изменение будет иметь слишком много последствий.

Итак, как я могу создать ассоциацию "user has_many: tickets" через столбец login ?

Я попробовал следующее finder_sql, но оно не работает.

class User  < ActiveRecord::Base
  has_many :tickets,
      :finder_sql => 'select t.* from tickets t where t.user_login=#{login}'
  ...
end

Я получаю странную ошибку:

ArgumentError: /var/lib/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:402:in `to_constant_name': Anonymous modules have no name to be referenced by
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/base.rb:2355:in `interpolate_sql'
    from /var/lib/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:214:in `qualified_name_for'
    from /var/lib/gems/1.8/gems/activesupport-2.0.2/lib/active_support/dependencies.rb:477:in `const_missing'
    from (eval):1:in `interpolate_sql'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations/association_proxy.rb:95:in `send'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations/association_proxy.rb:95:in `interpolate_sql'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations/has_many_association.rb:143:in `construct_sql'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations/has_many_association.rb:6:in `initialize'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1032:in `new'
    from /var/lib/gems/1.8/gems/activerecord-2.0.2/lib/active_record/associations.rb:1032:in `tickets'
    from (irb):1

Я также попробовал этот finder_sql (с двойными кавычками вокруг логина):

:finder_sql => 'select t.* from tickets t where t.user_login="#{login}"'

Но он терпит неудачу таким же образом (и в любом случае, если бы он работал, он был бы уязвим для внедрения SQL).

В тестовой базе данных я добавил столбец user_id в таблицу заявок и попробовал этот finder_sql:

:finder_sql => 'select t.* from tickets t where t.user_login=#{id}'

Теперь это работает нормально. Очевидно, что моя проблема связана с тем, что столбец пользователей, который я пытаюсь использовать, - это строка, а не идентификатор.

Я довольно долго искал в сети ... но не мог найти подсказки.

Я бы хотел иметь возможность передать любой параметр в finder_sql и написать что-то вроде этого:

has_many :tickets_since_subscription,
:finder_sql => ['select t.* from tickets t where t.user_login=?'+
     ' and t.created_at>=?', '#{login}', '#{subscription_date}']

Редактировать: я не могу использовать параметр: foreign_key ассоциации has_many, потому что в моей таблице пользователей есть столбец первичного ключа id, используемый в другом месте приложения.

Редактирование # 2: по-видимому, я недостаточно внимательно прочитал документацию: ассоциация has_many может принимать параметр: primary_key, чтобы указать, какой столбец является локальным первичным ключом (идентификатор по умолчанию). Спасибо, Даниэль, за то, что открыл мне глаза! Я думаю, что это отвечает на мой оригинальный вопрос:

has_many tickets, :primary_key="login", :foreign_key="user_login"

Но мне все равно хотелось бы узнать, как заставить работать ассоциацию has_many: tickets_since_subscription.

Ответы [ 4 ]

4 голосов
/ 01 декабря 2008

Я думаю, вы хотите, чтобы опция :primary_key равнялась has_many. Это позволяет вам указать столбец в текущей таблице, чье значение хранится в столбце :foreign_key в другой таблице.

has_many :tickets, :foreign_key => "user_login", :primary_key => "login"

Я нашел это, прочитав has_many документы.

2 голосов
/ 12 мая 2009

Чтобы иметь что-то вроде has_many: tickets_since_subscription, вы можете использовать named_scopes:

В модель добавить:

named_scope :since_subscription, lambda { |subscription_date| { :conditions => ['created_at > ?', subscription_date] }

С этим вы можете найти то, что вы хотите, как это:

user.tickets.since_subscription 3.days.ago

или

user.tickets.since_subscription user.subscription_date

(конечно, вам нужен столбец subscription_date в модели пользователя).

Вы можете найти больше примеров здесь .

Если вы не хотите использовать named_scopes, вы можете найти то, что вам нужно, с помощью:

user.tickets.all(:conditions => ['created_at > ?', subscription_date]) 
1 голос
/ 30 ноября 2008

Я думаю, вы ищете вариант :foreign_key на has_many. Это должно позволить вам указать, что внешний ключ не user_id, а user_login, без настройки логики поиска.

Подробнее см. Документацию ActiveRecord has_many .

0 голосов
/ 30 ноября 2008

Просто отвечаю самому себе, если нет лучшего решения. Я не смог найти решение с ассоциацией has_many, поэтому в итоге я создал простой метод поиска. Совсем не здорово: он позволяет мне вызывать some_user.tickets , но не дает мне всех преимуществ ассоциаций has_many (а именно методов clear, delete, <<, ... сама ассоциация). </p>

def tickets
    return Ticket.find(:all, :conditions=>["user_login = ?", login])
end

Я все еще надеюсь, что кто-то найдет лучшее решение.

...