Rails create scope of duplicates records - ruby-on-rails

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) }

Related

Rails non hash conditions lost table reference

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

rails scope using "AND"

I have 2 scopes, the by_group_title does is used in a search box to return group names based off of a user search. It's working good, however, I'm trying to limit what groups can be returned in the search results. Specifically I'm trying to exclude any group that has a "membership" of "Restricted", I only want groups returned in the search results when the "membership" is set to "Standard" for the group.
The second scope I have is called by_standard_membership. This scope will return standard groups but it does not allow searching. So I'm trying to find a way to combine both scopes so that a user can search for a group title but only the standard groups are shown and the restricted groups do not appear in the search results.
I've been playing around with using an "and" statement to combine them but can't seem to get it working.
#scopes
scope :by_group_title, -> (group) { where('title LIKE ?',"%#{group}%" ).order(created_at: :desc) if group.present? }
scope :by_standard_membership, -> { where(membership: "Standard").order(created_at: :desc) }
There are a few ways you can do this. In the examples below, I have added the condition(AND) someNum = 1 to the expressions you provided.
You can either add AND to the where clause ...
scope :by_group_title, -> (group) { where('title LIKE ? and someNum = 1',"%#{group}%" ).order(created_at: :desc) if group.present? }
Or use the ActiveRecord fields ...
scope :by_standard_membership, -> { where(membership: "Standard", someNum: 1).order(created_at: :desc) }
Alternatively, you can chain the where clauses together ...
scope :by_standard_membership, -> { where(membership: "Standard").where(someNum: 1).order(created_at: :desc) }

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}) }

RubyOnRails multiple scopes composition

I got a Product model with has_many Types table and several scopes:
class Product < ActiveRecord::Base
has_many :product_types
has_many :types, through: :product_types
scope :type1, -> { joins(:types).where(types: { name: "Type1" }) }
scope :type2, -> { joins(:types).where(types: { name: "Type2" }) }
end
When I try to use one scope (Product.type1 for example) all goes well, but two scopes at a time (Product.type1.type2) returns an empty query. Yes, one product may have multiple types.
Final goal is to create a filter of products by type with checkboxes form. When I check type1 and type2 I want to see all my products that have Type1 and Type1 at the same time.
UPD 1
So I've tried to do several queries and then & them as #aaron.v suggested. I wanted to do the logic inside of the function so:
def products_by_type_name(types)
all_types = types.each { |type| Product.joins(:types).where(types: { name: type }).distinct }
...
end
My point was to iterate through each type, collect all products and then & them inside the function.
The problem is when I'm iterating, each loop returns string instead of array of hashes.
Product.joins(:types).where(types: { name: types }).distinct # returns array of hashes, it's okay.
types.each { |type| Product.joins(:types).where(types: { name: type }).distinct } # each loop returns string (Type name) instead of array of hashes.
What am I doing wrong?
SOLUTION 1
Suggested by #aaron.v, explained below
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
SOLUTION 2
Found on reddit.
In this function you are fetching all products with at least one required type and than grouping only ones that have required count of types. Thus, you get only those products that belongs to every type at the same time.
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct.group('products.id').having('count(*) = ?', types.each.count)
end
If you have a database background, this would be pretty obvious as to why you wouldn't be able to find products with multiple types based off how you are writing your scopes.
Database queries that are written from these scopes will multiply the rows to ensure that a product that has many types, will have a distinct row for each type. Your query when you combine both scopes is writing
where `types`.name = "Type1" AND `types`.name = "Type2"
This will never happen since columns aren't added with multiples of the same row on a join.
What you want to do is have a method that you can pass an array of type names to find it
def self.by_type_name(types)
joins(:types).where(types: { name: types }).distinct
end
This method can accept one name by itself or an array of names for the types you want to find
If you pass an array of type names like ["type1", "type2"], this will result in a query like
where `types`.name in ("type1", "type2")
And then you should see the results you expect
UPDATE
To revise what you need in finding products that have all types given to your method, I decided to do this
def self.by_type_name(types)
product_ids = []
types.each do |t|
product_ids << (joins(:types).where(types: { name: t }).distinct.select(:id).map(&:id))
end
find(product_ids.inject(:&))
end
This method requires an array of type names(even if it is one type, pass an array with one type in it). What it will do is find all products with one specific type name, only select the product id(which will be lighter on your DB) and map it into an array which will get dumped into the product_ids array. after it has looped over each type name, it will find all products that intersect in each of the arrays resulting with products that have all types passed in.

Scope on number of items in associated model

Suppose we have a Tag model with many associated Post (or none) via a has_many association.
Is there an efficient way to select only the tags that do have a tag.posts.size > 0 via a scope ?
Something that would behave as follows:
scope :do_have_posts, -> { where("self.posts.count > 0") } #pseudo-code
Thanks.
This should only return you the tags with posts since rails does an inner join by default
scope :with_posts, -> { joins(:posts).uniq }

Resources