As an example I forked the ransack_demo app and added a scope to the Comment model, 'created_after'. I can now use ransack to search the Comment model using this scope, but I can't search it via the User model:
Comment.search(:created_after=>'2014-01-01').result.to_sql
=> "SELECT \"comments\".* FROM \"comments\" WHERE (created_at>'2014-01-01')"
vs:
User.search(:comments_created_after=>'2014-01-01').result.to_sql
=> "SELECT \"users\".* FROM \"users\"
But normal searches across the association work fine:
User.search(:comments_body_cont=>'hi').result.to_sql
=> "SELECT \"users\".* FROM \"users\" LEFT OUTER JOIN \"comments\" ON \"comments\".\"user_id\" = \"users\".\"id\" WHERE (\"comments\".\"body\" ILIKE '%hi%')"
The view pukes with:
undefined method `comments_created_since' for Ransack::Search
My modified demo is at: https://github.com/cotcomsol/ransack_demo
Hi @cotcomsol, thanks for the input. Scopes in Ransack are a recent addition since June, and I'm not sure they work through associations yet. To be honest, I didn't need scopes (using ransackers instead) and so far don't use them, so someone who does use them could hopefully chime in about it.
Hmm. I think this should be working, at least I know that we use it successfully... I'll take a closer look when I have a minute.
I also have a patch almost ready to fix default scopes on associations, which is a closely related issue, but also its own beast.
Thanks @avit :smiley:
@cotcomsol your bundle is locked to ransack 1.2.2 which is older than the ransackable scopes feature:
Guessing that's likely your problem... You can also use this option to see if your conditions aren't recognized:
Ransack.configure do |config|
config.ignore_unknown_conditions = false
end
Please try updating and let us know.
I actually updated the demo to 1.3.0 before I did my testing but forgot to commit the updated Gemfile.lock. I just did that now. It definitely doesn't work for me even with 1.3.0.
created_after to created_since?The created_after/crated_since was a typo on my part. I did most of my testing on the console and didn't really worry about through the web. I did fix it though and it behaves the same.
I'm not doing any special joins or queries. Just the commands from the console example is essentially all I am doing to duplicate the issue. I don't know why it is doing a left outer join.
I did try upgrading to 3.2.19 and had the same results.
If I use the ignore_unknown_conditions=false, the web interface behaves the same. The console results are different though:
Comment.search(:created_after=>'2014-01-01').result.to_sql
=> "SELECT \"comments\".* FROM \"comments\" WHERE (created_at>'2014-01-01')"
vs
User.search(:comments_created_after=>'2014-01-01').result.to_sql
ArgumentError: Invalid search term comments_created_after
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/search.rb:40:in block in build'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/search.rb:32:ineach'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/search.rb:32:in build'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/search.rb:24:ininitialize'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/adapters/active_record/base.rb:16:in new'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/ransack-1.3.0/lib/ransack/adapters/active_record/base.rb:16:inransack'
from (irb):2
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/railties-3.2.17/lib/rails/commands/console.rb:47:in start'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/railties-3.2.17/lib/rails/commands/console.rb:8:instart'
from /home/brock/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/railties-3.2.17/lib/rails/commands.rb:41:in <top (required)>'
from script/rails:6:inrequire'
from script/rails:6:in `
which seems to confirm that Ransack is not seeing that scope as an available condition when accessed via an associated model.
I appreciate everyone's feedback and looking into this issue.
Aha, I only thought this was working because we have this functionality in our app, but we've worked around it by defining our own ransackers and a formatter that changes "attributes" to "scopes"... all dirty hacks before I worked on the ransackable_scopes extraction.
So I was confused. ransackable_scopes only supports the base model you're searching on. If you want to search scopes on associated models, it gets more complex. I'll see about extracting what we have into a more general solution.
You can currently work around it by defining the scope on the parent model:
User.scope :with_comments_since, ->(date) {
joins(:comments).merge(Comment.created_after(date))
}
I don't know why it is doing a left outer join.
Because some conditions require it, like IS NULL. (Part of our internal hacks includes using subqueries for these things to make sure there's no crossed wires with multiple joins.)
OK that's a good idea, I got my particular case working by moving the scope up to the parent model. Thanks for the suggestion!
Great to see this clarified and a solution found. Thanks again @avit. Would you like to add your suggestion to the Scopes section of the README? Something along the lines of saying that searching through scopes on the associated model isn't yet possible and for now you'll need to define the association scope on the parent model. I've been intending to add more documentation about the new scopes because they are not well-documented yet, and this would be useful for people to know.
Most helpful comment
Aha, I only thought this was working because we have this functionality in our app, but we've worked around it by defining our own ransackers and a formatter that changes "attributes" to "scopes"... all dirty hacks before I worked on the
ransackable_scopesextraction.So I was confused.
ransackable_scopesonly supports the base model you're searching on. If you want to search scopes on associated models, it gets more complex. I'll see about extracting what we have into a more general solution.You can currently work around it by defining the scope on the parent model: