Is there a way to order records alphabetically, excluding one record, which I want at the end?
class Category < ActiveRecord::Base
default_scope -> { order(:name) }
end
I always want the category named 'Other' to be at the end.
You can try the below ActiveRecord query
Category.order("CASE WHEN name = 'Other' then 0 else 1 END DESC, name ASC")
This is a little bit tricky. In SQL you can add CASE statements to your ORDER BY. In your case the SQL would be something similar to.
SELECT *
FROM categories
ORDER BY
(
CASE
WHEN name = 'Other' THEN 1
ELSE 0
END
)
Here's a live example.
As far as I know, the ActiveRecord order method accepts arbitrary string, so you could (not tested) be able to pass the case to the method
Category.order("CASE WHEN name = 'Other' ... ")
This approach seems complicated, but if you can get it to work is by far the most efficient.
The second alternative is to play a little bit with ActiveRecord.
class Category < ActiveRecord::Base
def self.ordered(condition = nil)
# Get the ID of the record to exclude
excluded = self.where(name: 'Other').pluck(:id).first
# order the records and collect the ids
ids = where('id <> ?', excluded).order(condition).pluck(:id)
# append the excluded at the end
ids << excluded
# recreate the scope and return it.
where(id: ids)
end
end
Category.where(...).ordered
Generally speaking, I encourage you to avoid default_scopes in ActiveRecord. It's so easy to add them, but very hard to remove them when you need.
I would recommend just to add an additional field "position" and sort everything by it. Or you can order by 2 fields (position, name). All Categories will have position equal = 0, and "Other" equal to 999 for example
Related
Update 2
Cama::PostType.first.posts.joins(:custom_field_values)
.where("cama_custom_fields_relationships.custom_field_slug = ? AND
cama_custom_fields_relationships.value LIKE ?","localization",
"%Paris%").merge(Cama::PostType.first.posts.joins(:custom_field_values)
.where("cama_custom_fields_relationships.custom_field_slug = ? AND cama_custom_fields_relationships.value = ?","type-localization", "2"))
Why this merge doesn't work ?
It returns me same result when executed seperately... Merge should work as intersection so common part should be result. I dont get it
Update
I will try to ask in more conceptual way.
I have model B that have slug:text, value:text, belongs_to: Model A
I have model A that have name:string, has_many: Model B
#posts_one = I search for model B where slug="something", value = "city"
#posts_two = I search for model B where slug="mood", value="good"
I have 2 results based on diffrent parameters. Both belongs_to: model A
Now I want to return only the common belongs_to.
so if
#posts_one will return me 20 results with model_a_ids
#posts_two will return me 20 results with model_a_ids
I want to return only common model_a_ids of those 2 queries and right away to find posts. I try to make it in one query but dont know if its possible
Oryginal post
I use Camaleon CMS and I try to create filters based on additional "custom fields". I think to answer this question you dont have to know this cms.
I want to find common part of 2 queries or make it in one query(that would be the best)
I have
#posts = Cama::PostType.first.posts.includes(:custom_field_values)
#param_localization = "Paris"
#param_type_localization = "House"
#posts_one = #posts.merge(CamaleonCms::CustomFieldsRelationship.
where("cama_custom_fields_relationships.custom_field_slug = ? AND
LOWER(cama_custom_fields_relationships.value) LIKE ?", "localization",
"%#{#param_localization}%"))
puts #posts_one.count => 2
#posts_two = #posts.merge(CamaleonCms::CustomFieldsRelationship.where(custom_field_slug:
"type-localization", value: #param_type_localization))
puts #posts_two.count => 2
Question is how can I merge it together or make it one query ? When I made it in one where clause it returns me 0 results since I need to find 2 diffrent custom fields relationships that has diffrent values and slugs but it have relations to posts throught :custom_fields_values, so I have to make 2 queries I guess(like I did). First I find customFieldRelationship with slug = localization and second with slug = type_localization and then I need to find common part
I tried to #result = #posts_one.merge(#posts_two) but I got no result then. I thought it will return me "common part" of association which means 2 results
How can I combine it to find me posts that fullfil both queries ?
Let me know if I explained my problem not well enought.
You'll want to combine it in SQL: (untested)
#posts_combined = #posts.merge(CamaleonCms::CustomFieldsRelationship.
where("(cama_custom_fields_relationships.custom_field_slug = ?
OR cama_custom_fields_relationships.custom_field_slug = 'type-localization')
AND LOWER(cama_custom_fields_relationships.value) LIKE ? ", "localization",
"%#{#param_localization}%"))
class Parent < ApplicationRecord
has_many :children
enum status: {
status1: 0,
status2: 1
}
end
class Child < ApplicationRecord
belongs_to :parent
end
# error
# "Relation passed to #or must be structurally compatible. Incompatible values: [:references]"
combination = Parent.status1.or(Parent.status2.includes(:children).where(children: {name: 'ABC'}))
I want to get the data "status1" or "status2 has children named 'ABC'", but error occurs.
The or method takes another relation that has a similar filter pattern, and combines it with the already-existing filters on the object being called.
For example, Parent.status1.or(Parent.status2) would give you a set of records that have either status: 1 or status: 2.
(In case someone is not familiar with it, the example in the question also uses enum, which allows filtering the enum's attribute value using the name of the value. #status1 and #status2 in this case correspond to { status: 0 } and {status: 1} respectively.)
In order to call more relation methods to modify the final result, you must call them on the result of calling #or, like this:
Parent.status1.or(Parent.status2).includes(:children).where(children: {name: 'ABC'})
Based on your comment I see now that you want records that either (have status1) or (have status2 and have a matching children record).
Note that in order to use a relation in a where (like where(children: { name: value }) you must join with the related table (joins(:children).where(children: { name: value }). It seems that ActiveRecord will infer the join if you use only includes, but that's not documented as far as I can tell. This is why or sees the two relations as incompatible: one has children in the references list, while the other does not.
If you write the where clause by hand as a string, it does not change the references list, so or does not see the relation as incompatible. When you write a where clause by hand, you must explicitly use joins:
Parent.status1.joins(:children).or(Parent.status2.joins(:children).where("children.name = 'ABC'"))
You are not calling "includes" on the final or result.
parent = Parent.status1.or(Parent.status2)
parent.includes(:chilren).where(children: {name: "ABC"})
Suppose I've got ActiveRecord models such that User has_one :photo. In the database, photos has a t.binary column which may hold a lot of data, so I don't want to SELECT that column unless I need to.
I want to do something like:
users.each do |user|
image_tag(user_photo_path) if user.photo.present?
end
However, I don't want to call user.photo.present? because:
Doing so loads the photo association, including SELECT * from photos
Even if it could be made to only SELECT id FROM photos to check existence, it's still an N + 1 query.
What I really want is to load users with a single query which gives each one a property telling me whether it has an associated photo or not.
With ActiveRecord 5, this works:
class User < ActiveRecord::Base
scope :with_photo_id, -> {
left_outer_joins(:photo).select(
"users.*, photos.id AS photo_id"
)
}
end
Then I can call User.with_photo_id and check user.photo_id.present?.
Prior to AR 5, the join would be uglier:
joins(
"LEFT OUTER JOIN photos ON photos.user_id = users.id"
)
In SQL I would do a "select count(*), min(price) from products". What is the best way to do this with one query in active records?
Right now I have to do two queries, which doesn't feel right.
result_count = Product.where(filter_string).count
result_min = Product.where(filter_string).minimum(:price)
You can add the string in a select method like this:
result_stats = Product.select("count(*) as product_count, min(price) as price_min").where(filter_string)[0]
result_stats["product_count"] # => 123
result_stats["price_min"] # => 12.35
The limitation here is that this will initialize Product objects with only those fields accessible (in this case only 1 object since there's no group by clause). In this case, it's not really an issue, but something worth knowing if you get an error when you try to access relations.
http://guides.rubyonrails.org/active_record_querying.html#selecting-specific-fields
I always wondered how to query and get results that doesn't fit in a model. Similar how it's done using LINQ and projecting into anonymous objects.
So here's the simple schema:
# Product.rb
class Product < ActiveRecord::Base
has_many :product_views
# attributes: id, name, description, created_at, updated_at
end
# ProductView.rb
class ProductView < ActiveRecord::Base
belongs_to :product
# attributes: id, product_id, request_ip, created_at, updated_at
end
Basically I need to get a list of Products (preferably just id and name) along with the count of views it had. Obviously ordered by view count desc.
This is the SQL I want to get:
select
p.id,
p.name,
count(pv.product_id) as views
from
product_views pv
inner join
products p on pv.product_id = p.id
group by
pv.product_id
order by
count(product_id) desc
I tried the following and similar, but I'm getting ProductView objects, and I would like to get just an array or whatever.
ProductView.includes(:product)
.group('product_id')
.select("products.id, products.name, count(product_id)")
This kind of thing are trivial using plain SQL or LINQ, but I find myself stucked with this kind of queries in Rails. Maybe I'm not thinking in the famous 'rails way', maybe I'm missing something obvious.
So how do you do this kind of queries in Rails 3, and specifically this one? Any suggestions to improve the way I'm doing this are welcome.
Thank you
You can use Arel to do what you're looking for:
products = Product.arel_table
product_views = ProductView.arel_table
# expanded for readability:
sql = products.join(product_views)
.on(product_views[:product_id].eq(product[:id]))
.group(product_views[:product_id])
.order('views DESC')
.project(products[:id],
products[:name],
product_views[:id].count.as('views'))
products_with_views = Product.connection.select_all(sql.to_sql) # or select_rows to just get the values
Yes, it is long, but Arel is a very smart way to deal with creating complex queries that can be reused regardless of the database type.
Within a class method in the Product class:
Product.includes(:product_views).all.map { |p| [p.id, p.name, p.product_views.size] }
Then sort it however you want.
I don't know if there's a way to do it using your models. I would probably resort to:
Product.connection.select_rows(sql)
Which will give you an array of arrays. You can use select_all if you'd rather have an array of hashes.
Try this:
#product = Product.find(#product_id)
#product_views = #product.product_views.count
(Source - http://ar.rubyonrails.org/classes/ActiveRecord/Calculations/ClassMethods.html#M000292)
Hope this helps!