59

What I want is to create a Model that connects with another using a has_many association in a dynamic way, without the foreign key like this:

has_many :faixas_aliquotas, :class_name => 'Fiscal::FaixaAliquota',
            :conditions => ["regra_fiscal = ?", ( lambda { return self.regra_fiscal } ) ]

But I get the error:

: SELECT * FROM "fis_faixa_aliquota" WHERE ("fis_faixa_aliquota".situacao_fiscal_id = 1
AND (regra_fiscal = E'--- !ruby/object:Proc {}'))

Is this possible?

John Topley
  • 113,588
  • 46
  • 195
  • 237
Fabiano Soriani
  • 8,182
  • 9
  • 43
  • 59

6 Answers6

110

Rails 4+ way (Thanks to Thomas who answered this below):

has_many :faixas_aliquotas, -> (object) { 
           where("regra_fiscal = ?", object.regra_fiscal)
         },
         :class_name => 'Fiscal::FaixaAliquota'

Rails 3.1+ way:

has_many :faixas_aliquotas, :class_name => 'Fiscal::FaixaAliquota',
         :conditions => proc { "regra_fiscal = #{self.regra_fiscal}" }

Rails 3 and below:

has_many :faixas_aliquotas, :class_name => 'Fiscal::FaixaAliquota',
         :conditions => ['regra_fiscal = #{self.regra_fiscal}']

No. This is not a mistake. The conditions are specified in single quotes and still contains the code #{self.regra_fiscal}. When the conditions clause is evaulated, the regra_fiscal method will be called on the object of self (whatever the class is). Putting double quotes will not work.

I hope this is what you are looking for.

Chirantan
  • 15,304
  • 8
  • 49
  • 75
  • 1
    +1 Just tried, and got: >> SituacaoFiscal.last.faixas_aliquotas NameError: uninitialized constant SituacaoFiscal from /home/fabiano/workspace/sgi/vendor/rails/activesupport/lib/active_support/dependencies.rb:443:in `load_missing_constant' from /home/fabiano/workspace/sgi/vendor/rails/activesupport/lib/active_support/dependencies.rb:80:in `const_missing' from /home/fabiano/workspace/sgi/vendor/rails/activesupport/lib/active_support/dependencies.rb:92:in `const_missing' likely because my class name is: 'Fiscal::SituacaoFiscal' – Fabiano Soriani Mar 17 '10 at 13:15
  • I'm not sure how to handle the scope, you might want to try some stuff around this solution. – Chirantan Mar 17 '10 at 13:36
  • Worked perfectly now! Likely I was doing something wrong in my class before; tnxs! – Fabiano Soriani Mar 17 '10 at 17:05
  • I tried all possibilities to get that work except this one! I would give you +10 if I could. – True Soft Nov 20 '10 at 13:31
  • Need proc from Rails 3.1: :conditions => proc { ["regra_fiscal = #{self.regra_fiscal}"] (Yes, double quotes again :). – mahemoff Dec 26 '11 at 20:47
  • An additional single quote was necessary for me in Rails 3.2.13: :conditions => proc { ["regra_fiscal = '#{self.regra_fiscal}'"] } – TimeEmit Mar 20 '13 at 20:27
  • 6
    Better approach is to use bind variable to avoid SQL injection, i.e. `proc { ["regra_fiscal = ? ", regra_fiscal] }` – Harish Shetty Apr 23 '13 at 19:00
  • 1
    Now that you can't use :condition anymore in Rails 4 but -> { where column: true }, how would you update the accepted solution to work with Rails 4? (for now, Rails 4 only display a deprecation message) – Thomas Dec 05 '13 at 22:07
  • I actually answered this in this very question http://stackoverflow.com/a/20412163/429941 – Thomas Apr 16 '14 at 20:32
  • Oh man, I would kiss you. Thank you very much good sir @Chirantan! – Yoko Jun 16 '16 at 08:17
  • You can also return a hash from the proc. Notice the double curly braces. One set is the block of the proc and the other is for the hash. `Proc.new{{:regra_fiscal => regra_fiscal}}` – Josh Jan 16 '17 at 23:32
65

Rails 4+ way:

has_many :faixas_aliquotas, 
         -> (object){ where("regra_fiscal = ?", object.regra_fiscal)},  
         :class_name => 'Fiscal::FaixaAliquota'
BryanH
  • 5,826
  • 3
  • 34
  • 47
Thomas
  • 1,956
  • 14
  • 21
9

There is another kind of solution. However, this wont be the default scope.

has_many :faixas_aliquotas, :class_name => 'Fiscal::FaixaAliquota' do 
  def filter(situacao_fiscal)
    find(:all, :conditions => {:regra_fiscal => situacao_fiscal.regra_fiscal})
  end
end

This way you would be able to do

situacao_fiscal.faixas_aliquotas.filter(situacao_fiscal)

I am not sure if this is elegant and something that would solve your problem. There may be better ways of doing this.

Chirantan
  • 15,304
  • 8
  • 49
  • 75
  • This is awesome and still works in Rails 5. This is great because I can use for association condition not only the object itself. – 鄭元傑 Aug 06 '21 at 04:30
5

Rails 4+ another way:

has_many :faixas_aliquotas, -> (object){ where(regra_fiscal: object.regra_fiscal) }, :class_name => 'Fiscal::FaixaAliquota'
GeoffreyHervet
  • 288
  • 5
  • 16
3

In Rails 3.1 need to use proc, Proc.new { "field = #{self.send(:other_field)}" }

Amala
  • 1,718
  • 1
  • 17
  • 29
  • I had to use a proc, not a lambda to get this to work. Thanks! – Adam May 31 '12 at 19:41
  • @Adam lambda works if you make it accept 1 arg. I have no idea what this arg is used for; I've only seen `nil`. Or you could use a lambda with variable args: `->(*args) { ... }` – Kelvin Sep 03 '14 at 21:55
  • As Harish Shetty commented on another answer, it's better to use bind variables to guard against sql injection: `proc { ['field = ?', self.send(:other_field)] }` – Kelvin Sep 03 '14 at 21:59
1

In Rails 3.1 you can use Proc.new for your conditions. as stated by @Amala, but instead generate a hash like this:

has_many :faixas_aliquotas, :class_name => 'Fiscal::FaixaAliquota',
   :conditions => {:regra_fiscal => Proc.new { {:regra_fiscal => self.regra_fiscal} }

The benefit of this approach is that if you do object.faixas_aliquotas.build, the newly created object will automatically have the same regra_fiscal attribute as the parent.

bayfieldcoder
  • 158
  • 2
  • 8