Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I pass a string to a has_many :finder_sql parameter?

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.

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

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

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 => '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).

In a test database, I added a user_id column in the tickets table, and tried this 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.

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:

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.

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:

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.

like image 865
MiniQuark Avatar asked Nov 30 '08 10:11

MiniQuark


2 Answers

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 :tickets, :foreign_key => "user_login", :primary_key => "login"

I found this by reading the has_many docs.

like image 78
Daniel Beardsley Avatar answered Oct 02 '22 14:10

Daniel Beardsley


To have something like has_many :tickets_since_subscription you can use 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).

You can find more examples here.

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

user.tickets.all(:conditions => ['created_at > ?', subscription_date]) 
like image 26
klew Avatar answered Oct 02 '22 12:10

klew