如何将字符串传递给has_many:finder_sql参数?

时间:2022-11-09 07:25:05

In my application, a user has_many tickets. Unfortunately, the tickets table does not have a user_id: it has a user_login (it is a legacy database). I am going to change that someday, but for now this change would have too many implications.

在我的应用程序中,用户has_many票证。不幸的是,票证表没有user_id:它有一个user_login(它是一个遗留数据库)。我有一天会改变这一点,但是现在这种改变会产生太多影响。

So how can I build a "user has_many :tickets" association through the login column?

那么如何通过登录列构建“用户has_many:ticket”关联?

I tried the following finder_sql, but it does not work.

我尝试了以下finder_sql,但它不起作用。

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

I get a weird error:

我得到一个奇怪的错误:

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

I also tried this finder_sql (with double quotes around the login):

我也尝试过这个finder_sql(在登录时使用双引号):

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

But it fails the same way (and anyway, if it worked it would be vulnerable to sql injection).

但它以同样的方式失败(无论如何,如果它工作,它将容易受到sql注入)。

In a test database, I added a user_id column in the tickets table, and tried this finder_sql:

在测试数据库中,我在ticket表中添加了一个user_id列,并尝试了这个finder_sql:

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

Now this works fine. So apparently, my problem has to do with the fact that the users column I am trying to use is a string, not an id.

现在这很好用。显然,我的问题与我尝试使用的用户列是字符串而不是id的事实有关。

I searched the net for quite some time... but could not find a clue.

我在网上搜了很长时间......但是找不到线索。

I would love to be able to pass any parameter to the finder_sql, and write things like this:

我希望能够将任何参数传递给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}']

Edit: I cannot use the :foreign_key parameter of the has_many association because my users table does have an id primary key column, used elsewhere in the application.

编辑:我不能使用has_many关联的:foreign_key参数,因为我的users表确实有一个id主键列,在应用程序的其他地方使用。

Edit#2: apparently I did not read the documentation thoroughly enough: the has_many association can take a :primary_key parameter, to specify which column is the local primary key (default id). Thank you Daniel for opening my eyes! I guess it answers my original question:

编辑#2:显然我没有彻底阅读文档:has_many关联可以采用:primary_key参数,指定哪个列是本地主键(默认id)。谢谢丹尼尔睁开眼睛!我想它回答了我原来的问题:

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

But I would still love to know how I can make the has_many :tickets_since_subscription association work.

但我仍然想知道如何使has_many:tickets_since_subscription关联工作。

4 个解决方案

#1


4  

I think you want the :primary_key option to has_many. It allows you to specify the column on the current Table who's value is stored in the :foreign_key column on the other table.

我想你想要has_many的:primary_key选项。它允许您指定当前Table上的列的值,该列的值存储在另一个表的:foreign_key列中。

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

I found this by reading the has_many docs.

我通过阅读has_many文档找到了这个。

#2


2  

To have something like has_many :tickets_since_subscription you can use named_scopes:

要拥有像has_many:tickets_since_subscription这样的东西,你可以使用named_scopes:

In model add:

在模型中添加:

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

With this, you can find what you want like this:

有了这个,你可以找到你想要的东西:

user.tickets.since_subscription 3.days.ago

or

user.tickets.since_subscription user.subscription_date

(of course you need subscription_date column in user model).

(当然,您需要在用户模型中使用subscription_date列)。

You can find more examples here.

你可以在这里找到更多的例子。

If you don't want to use named_scopes you can find what you want with this:

如果你不想使用named_scopes,你可以用这个找到你想要的东西:

user.tickets.all(:conditions => ['created_at > ?', subscription_date]) 

#3


1  

I think you are looking for the :foreign_key option on has_many. That should allow you to specify that the foreign key is not user_id, but user_login, without adjusting the finder logic.

我想你正在寻找has_many上的:foreign_key选项。这应该允许您指定外键不是user_id,而是user_login,而不调整finder逻辑。

See the ActiveRecord has_many documentation for more details.

有关更多详细信息,请参阅ActiveRecord has_many文档。

#4


0  

Just answering to myself, in case there is no better solution. I could not find a solution with the has_many association, so I ended up creating a simple finder method. Not great at all: it does allow me to call some_user.tickets, but it does not give me all the benefits of the has_many associations (namely the clear, delete, <<,... methods on the association itself).

只是回答自己,以防万一没有更好的解决方案。我找不到has_many关联的解决方案,所以我最终创建了一个简单的finder方法。根本不是很好:它确实允许我调用some_user.tickets,但它并没有给我所有has_many关联的好处(即关联本身的clear,delete,<<,...方法)。

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

I am still hoping that someone will come up with a better solution.

我仍然希望有人能提出更好的解决方案。

#1


4  

I think you want the :primary_key option to has_many. It allows you to specify the column on the current Table who's value is stored in the :foreign_key column on the other table.

我想你想要has_many的:primary_key选项。它允许您指定当前Table上的列的值,该列的值存储在另一个表的:foreign_key列中。

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

I found this by reading the has_many docs.

我通过阅读has_many文档找到了这个。

#2


2  

To have something like has_many :tickets_since_subscription you can use named_scopes:

要拥有像has_many:tickets_since_subscription这样的东西,你可以使用named_scopes:

In model add:

在模型中添加:

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

With this, you can find what you want like this:

有了这个,你可以找到你想要的东西:

user.tickets.since_subscription 3.days.ago

or

user.tickets.since_subscription user.subscription_date

(of course you need subscription_date column in user model).

(当然,您需要在用户模型中使用subscription_date列)。

You can find more examples here.

你可以在这里找到更多的例子。

If you don't want to use named_scopes you can find what you want with this:

如果你不想使用named_scopes,你可以用这个找到你想要的东西:

user.tickets.all(:conditions => ['created_at > ?', subscription_date]) 

#3


1  

I think you are looking for the :foreign_key option on has_many. That should allow you to specify that the foreign key is not user_id, but user_login, without adjusting the finder logic.

我想你正在寻找has_many上的:foreign_key选项。这应该允许您指定外键不是user_id,而是user_login,而不调整finder逻辑。

See the ActiveRecord has_many documentation for more details.

有关更多详细信息,请参阅ActiveRecord has_many文档。

#4


0  

Just answering to myself, in case there is no better solution. I could not find a solution with the has_many association, so I ended up creating a simple finder method. Not great at all: it does allow me to call some_user.tickets, but it does not give me all the benefits of the has_many associations (namely the clear, delete, <<,... methods on the association itself).

只是回答自己,以防万一没有更好的解决方案。我找不到has_many关联的解决方案,所以我最终创建了一个简单的finder方法。根本不是很好:它确实允许我调用some_user.tickets,但它并没有给我所有has_many关联的好处(即关联本身的clear,delete,<<,...方法)。

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

I am still hoping that someone will come up with a better solution.

我仍然希望有人能提出更好的解决方案。