Rails scope that does nothing for NOT IN values - ruby-on-rails-3

I have a Rails 3 scope that excludes an array of ids.
What is the best way to write the scope so that it does nothing when the array is empty and is still chainable? I currently have this, which works, but seems a little hokey:
scope :excluding_ids,
lambda {|ids| ids.empty? ? relation : where('id not in (?)', ids) }
If I do not have the "ids.empty? ? relation :" bit, when ids is empty the SQL generated is
... ID not in (NULL) ...
which will always return nothing. So something like:
Model.excluding_ids([]).where('id > 0')
returns no results.

If the ids array is empty then don't return anything.
scope :excluding_ids, lambda { |ids|
where(['id NOT IN (?)', ids]) if ids.any?
}
The query will run without any additional constraints on the query if there are no ids.

In Rails 4 you can use:
scope :excluding_ids, ->(ids) { where.not(id: ids) }

Here's a slight variation on Douglas' answer, using ruby 1.9 stabby lambda syntax and without the brackets in the where method.
scope :excluding_ids, ->(ids) {where("id NOT IN (?)", ids) if ids.any?}

How about the following? (It still checks for an empty array though, so if that's what you're trying to avoid it's not much of an improvement :)
scope :excluding_ids,
lambda {|ids| (ids.empty? && relation) || where('id not in (?)', ids) }

Related

Rails/SQL combine where conditions for rails scope

I want to get total count of items with language_id other than [30, 54] and language_id: nil
LOCALES = {
non_arabic_languages: {
id: [30, 54]
}
}
scope :non_arabic_languages, -> { where.not(language_id: LOCALES[:non_arabic_languages][:id]) || where(language_id: nil) }
This example predictably returns first part, so I only get non arabic items. && works wrong as well. How may I combine it? We'll be thankful for the advice!
You're falling into a common trap where you confuse logical operations in Ruby with actually creating SQL via the ActiveRecord query interface.
Using || will return the first truthy value:
where.not(language_id: LOCALES[:non_arabic_languages][:id]) || where(language_id: nil)
Which is the ActiveRecord relation returned by where.not(language_id: LOCALES[:non_arabic_languages][:id]) since everything except false and nil are truthy in Ruby. || where(language_id: nil) is never actually evaluated.
Support for .or was added in Rails 5. In previous versions the most straight forward solution is to use Arel or a SQL string:
scope :non_arabic_languages, -> {
non_arabic_languages_ids = LOCALES[:non_arabic_languages].map { |h| h[:id] }
where(arel_table[:language_id].not_in(non_arabic_languages_ids ).or(arel_table[:language_id].eq(nil)))
}
I would leave a tagging comment (like #fixme) so that you fix this after upgrading to 5.0 or consider a better solution that doesn't involve hardcoding database ids in the first place like for example using a natural key.

How to use lamda to get not equals value on a scope in ruby on rails?

I have a scope thats not currently working.
scope :not_voided, lambda { where('workflow_state != ?', "voided") }
I want to get all orders that don't have a workflow_state of "voided"
I'm really struggling with it and any help would be appreciated.
This code actually works as expected.

get model with atleast one relation object in mongoid

I have a Person object which has_many companies. I would like to get the person with atleast one company.
What I can get right now is
Person.where(:company_ids.size => 1)
This will return all the person with one company . But I need something like
Person.where(:company_ids.size.gte => 1)
But it seems , this does not work.
Solution :
sorry for all the trouble, but found out that with previously created objects , I didn't have company_ids ... since I had only added that later. I can get the count with following :
Person.where(:company_ids.exists => true).and("this.company_ids.length > 0")
Thanks everyone for helping out.
I assume company_ids a array field in person document
I am afraid there is no way of specifying conditions in size. But there is a workaround using javascript $where expression
db.person.find({$where: '(this.company_ids.length > 0)'})
am not sure about how to pass this expression in mongoid.
EDIT
yeah you can do this with mongoid too
Person.where("$where" => 'this.company_ids.length >0;' )
You should be able to do:
Person.where("this.company_ids.length > 3")
Did you check doing as
Person.where("this.company_ids >=1")

Rails 2-cases scope

I have scope
scope :for_list, lambda { |brand_ids|
where('brands.id IN (?)', brand_ids).includes(:models).where("models.popular = '1'").order('models.name')
}
But some times there are no models.popular = 1
And in this case I want to select all from models ignoring popular parameter
How to write that scope?
Since it's simply a lambda function, you can use an if/else in there and it'll get evaluated at the time it's called. Therefore, you could write the scope like this:
scope :for_list, lambda { |brand_ids|
if self.popular > 1
where('brands.id IN (?)', brand_ids).includes(:models).where("models.popular = '1'").order('models.name')
else
where('brands.id IN (?)', brand_ids).includes(:models).order('models.name')
end
}
That should work. However, I'd caution against using this. That logic doesn't belong in a scope. Instead, you should question you your views and controllers are set up and rework those.

Method 'Boolean Contains(System.String)' has no supported translation to SQL

"Method 'Boolean Contains(System.String)' has no supported translation to SQL."
query is IsQueryable but this stopped working:
foreach (string s in collection1)
{
if (s.Length > 0)
{
query = query.Where(m => m.collection2.Contains(s));
}
}
UPDATE: it works when i make query "ienumerable" instead of iqueryable. What would be the way to get same result using linq instead of iterating through loop?
Try this:
query = query.Where(m => m.collection2.ToList().Contains(s));
^^^^^^^^
Take a look at this answer from stackoverflow.
It looks like the resulting query would need access to something that the database
has no way of reaching, because the info is in memory.
Since m.collection2 is in the database, don't use Contains. Use Any
m.collection2.Any(x => x == s)
It looks like the error you are seeing is coming from the collection collection 2. Have you tried wrappering the m.collection2 in another function which returns true or false? Is this LINQ syntax?