Rails non hash conditions lost table reference - ruby-on-rails

When using array or string conditions inside Rails query, for example:
Scope in Location model:
scope :name_like, ->(keyword) {where("name ilike ?", keyword)}
It will have problem when using it with join table who also has column name. It is like:
Location.joins(:users).name_like('main')
It will report ambiguous column name conflicts at name.
How should I address this issue, thanks!

Change your name_like scope to use explicit name of locations. I suggest to change it as below:
scope :name_like, -> (keyword) { where("locations.name ilike ?", keyword) }

You need to use like this
scope :by_name, -> { joins(:users).where("users.name like '%?%'",'FirstName' ) }
Refer this link below.
https://apidock.com/rails/ActiveRecord/NamedScope/ClassMethods/scope

Related

Sort by a model method in a scope

In my model, I have this method which takes the last_name and first_name columns for an object and concatenates them into a string.
def name
last_name + " " + first_name
end
I want to define a scope that can sort my objects by that method. How would one go about doing that, using my method? I don't want to define some scope that first sorts by last_name, and then by first_name in the scope (if that's even possible). My understanding that you can only scope on actual columns in the rails framework? Is that incorrect?
Here's what I wrote, but obviously neither works, as there is no name field in my AdminUser table. Not sure why the second one doesn't work, but I'm guessing that the :name_field wouldn't work, as it's not actually in the model/database as a column.
scope :sorted, lambda { order("name ASC")}
scope :sorted, lambda { :name_field => name, order("name_field ASC")}
Unfortunately it is not possible to do this directly from Ruby/Rails to SQL. You could achieve what you want in two ways
You can load all the users into memory and sort them in Ruby
User.all.to_a.sort_by(&:name)
Or you can define an order in SQL as such
ORDER BY CONCAT(users.last_name, ' ', users.first_name) ASC;
In Rails, you'd have to do the following
scope :sorted, -> {
order("CONCAT(users.last_name, ' ', users.first_name) ASC")
}
Do note that this may not be portable between DBs.

Rails: ActiveRecord case-insensitive sort with dynamic table name?

[ Rails: ActiveRecord db sort operation case insensitive ] shows how to perform a case-insensitive sort with ActiveRecord.
Rails: ActiveRecord db sort operation case insensitive
Table.order("lower(column) DESC")
The code I am working with requires column name to be represented as a symbol so that ActiveRecord will automatically expand it to "table"."column". This is required, because some queries contain a join statement with ambiguous column names.
GitLab CE: app/models/concerns/sortable.rb#L19-20
scope :order_name_asc, -> { reorder(name: :asc) }
scope :order_name_desc, -> { reorder(name: :desc) }
The table can't be hard coded into the method, because it is an abstract class used for several different tables.
Is there a way to get the table name like ActiveRecord does?
scope :order_name_asc, -> { reorder(%Q{LOWER("#{???}"."name") ASC}) }
scope :order_name_desc, -> { reorder(%Q{LOWER("#{???}"."name") DESC}) }
Is there a way to use a symbolic column name and LOWER together and let ActiveRecord expand the table name?
Edit: Fixed typo using backticks instead of double quotes in last example.
ActiveRecord provides table_name method for models.
So
class User < ActiveRecord::Base
end
User.table_name
#=> "users"
Thus, this can be used in scope:
scope :order_name_asc, -> { reorder(%Q{LOWER("#{table_name}"."name") ASC}) }

Rails create scope of duplicates records

If you need to pull all duplicates by name in the class you can achieve it by:
Company.select(:name).group(:name).having("count(*) > 1")
By what to do if you want it in the scope
scope :duplicates, -> { where (...?)}
Also in return I need few fields not only name. Did anyone had the same problem to create a scope?
You need to run this in two queries. The first query selects the duplicate names, the second one selects the records with those duplicate names and uses the current_scope so that it can be chained with more scopes if needed (unfortunately current_scope seems to be a very useful but undocumented method):
scope :duplicates,
-> {
dup_names = Company.group(:name).having("count(*) > 1").pluck(:name)
current_scope.where(name: dup_names)
}
(The dup_names variable will contain an array of duplicate names found among the companies.)
Then you can easily add further conditions on the duplicate records, for example:
Company.duplicates.where("name like 'a%'").limit(2)
will select just two companies with the name starting with 'a' (and with duplicate names).
Since
scope :red, -> { where(color: 'red') }
is simply 'syntactic sugar' for defining an actual class method:
class Shirt < ActiveRecord::Base
def self.red
where(color: 'red')
end
end
you could define scope like this:
scope :duplicates, -> { ids = select(:id).group(:name).having("count(name) > 1"); where(id: ids) }

Is the correct: "Employee.where(age: <"60").order(:first name)"?

I am trying to use 'Employee.where(age: <"60").order(:first name)'. It is not working. Is there something wrong?
Assuming your :age field is an integer and you need to order by :first_name it goes like this:
Employee.where('age < ?', 60).order(:first_name)
Please take a look into documentation. You will find the descriptions to each ActiveRecord query method, including where and order.
I would do it like:
#Controller
#employees = Employee.sort_by_name('first_name ASC').young_employees
#model
scope :sort_by_name, ->(criteria) { order: criteria }
scope :young_employes, -> { where("age < ?", 60)}
In order to this to work, please check that your 'age' columns is integer type, and also that your first name columns is called 'first_name'

Scope based on many possible exclusions?

How do I create a scope that basically says this:
scope :filtered, where('status != one string, another string, yet another, yep, even more')
So basically saying "status does not equal any of the following strings."
And that list of strings will expand over time.
Note, I'm using PostgreSQL.
Try this: (Not tested)
scope :filtered, where(["status <> ALL ( ARRAY[?, ?] )", 'string_1', 'string_2'])
You can use this:
scope :filtered, where(["status NOT IN (?)", ["string1", "string2", "string3"]])

Resources