Suppose I have an Employee model, there is a method in Employee model named def fixed
def fixed
return self.cached_fixed.to_f if self.cached_fixed.present?
return (self.current_salary && self.current_salary.fixed).to_f
end
end
def current_salary
return #current_salary if #current_salary
# #current_salary = self.employee_salaries.detect{|es| es.end_date == nil}
#current_salary = self.db_current_salary
return #current_salary
end
if the fixed were a column in employee table we could have just used Employee.distinct.select(:fixed) to pull the distinct values
is there a way if it's just a method not a field in table without loading all the employees.
I am expecting to get the unique values of a column from a table , but it may not be a column as in the above table
Not for an arbitrary method, no. But you start the query from the EmployeeSalary end and fetch only the column you care about in one query using select:
EmployeeSalary
.select(:fixed)
.join(:employee)
.where(end_date: nil)
This will run a select fixed from... query and return a list of EmployeeSalary objects, but all the fields that aren't listed in the select call will be nil. Assuming the constraint of only one salary record having end_date: nil, there will be one EmployeeSalary object per employee. You can add .distinct in the method chain if you want unique values.
I'm not sure how the caching logic fits into this question. You can apply caching logic on top of that list if you like, but doing one query like this is pretty fast.
I have a table which contains an integer based column (status) which I'm using for an enum attribute within the Rails model.
At the moment of doing:
Post.select(:id, ..., :status)
Defined as:
enum status: { inactive: 0, active: 1, ... }
It returns everything as expected, but the status column is returned in its string value as inactive, active, etc. But I need it as an integer.
How can I get that?
I'm currently just using ActiveRecord::Base.connection.execute and passing a raw query:
ActiveRecord::Base.connection.execute('select id, ..., status from posts')
It won't be the most elegant way but this is the only way I see possible without looping over your objects.
You can achieve this by creating 2 classes like this:
class Post < ApplicationRecord
end
And the other one:
class PostSave < Post
enum status: { inactive: 0, active: 1, ... }
end
With this when you use Post class you won't get enum value for the "status" column but when you use PostSave class you can use your enum as you were already using.
Now when you do
Post.select(:id, :status)
It will give you integer values for the "status" column as you desire.
Yep. And that's simply how enum field works in Rails. Data is stored as integer, but displayed as string - based on what's defined in enum:
https://api.rubyonrails.org/v5.2.3/classes/ActiveRecord/Enum.html
If you really want to get integer try one of those:
Remove enum declaration. Data is stored as Integer. Without that line query will return Integer
Thats not the most beautiful code but: Post.statuses[post.status] will work
Have you tried this?
# Rails < 5
post = Post.find(123)
post.read_attribute(:status)
# Rails >= 5
post = Post.find(123)
post.read_attribute_before_type_cast(:status)
You can give this a try:
Post.select(:id, ..., 'status as int_status')
I'm not sure this directly applies as I do not know Rails or your exact requirement. But you indicated the desire to "get a collection of records from the DB"; so perhaps it will.
Postgres contains 2 tables in pg_catalog you need to join to get the collection values for a enum: pg_type and pg_enum as follows (where ENUM_NAME is replaced by the appropriate enum type name:
select t.typname,e.enumsortorder, e.enumlabel
from pg_type t
join pg_enum e
on (t.oid = e.enumtypid)
where t.typname = 'ENUM_NAME'
order by e.enumsortorder;
One difference I've notice is that in Postgres the associated numeric value is defined as Real, not as Integer. This is due to how the values are actually maintained. Example:
create type t_ord_stat as enum ('Ordered', 'Delivered', 'Billed', 'Complete', 'Canceled');
select e.enumsortorder, e.enumlabel
from pg_type t
join pg_enum e
on (t.oid = e.enumtypid)
where t.typname = 't_ord_stat'
order by e.enumsortorder;
alter type t_ord_stat add value 'Suppended: Out-of-Stock' after 'Ordered';
alter type t_ord_stat add value 'Suppended: Credit Check' before 'Suppended: Out-of-Stock' ;
alter type t_ord_stat add value 'Suppended: Customer Refused Delivery' before 'Billed';
select e.enumsortorder, e.enumlabel
from pg_type t
join pg_enum e
on (t.oid = e.enumtypid)
where t.typname = 't_ord_stat'
order by e.enumsortorder;
The above gives the actual numeric values underlying the enum string value (as column name indicates it's for sorting purpose). You could get a relative integer value with
select row_number() over() relative_seq, en.enumlabel
from (select e.enumsortorder, e.enumlabel
from pg_type t
join pg_enum e
on (t.oid = e.enumtypid)
where t.typname = 't_ord_stat'
order by e.enumsortorder) en;
I'll leave the actual conversion to Rails to you. Hope it helps.
You could map the column in the SQL statement to another field and read it from there instead, that way Rails passes the value directly as it doesn't know it's an enum (it doesn't know the column at all):
Post.select('id, title, status, status AS status_int').each do |post|
puts post.status # 'inactive'
puts post.status_int # 0
end
If you HAVE to have the same column name, then you're out of luck unless you do a little bit more work:
class Post
STATUSES = %w(inactive active)
end
Post.all.each do |post|
index = Post::STATUSES.index(post.status)
# use 'index' here instead of `post.status`
end
If neither of those are sufficient then this most certainly sounds like the http://xyproblem.info/ ... as these answers should work for 99% of cases. So you should probably explain WHY you cant just use status_int while working with the objects, or why you can't store the int in a variable, or... why you need access to the integer at all, which defeats the purpose of an enum.
The above solutions are working. I have tried with raw sql on mysql version 5.7.28.
the below query is working with mysql only.
I am still looking query for postgres, I will let you know soon.
create table Posts(id integer, title varchar(100), status ENUM ('active', 'inactive') NOT NULL);
insert into Posts(id, title, status) values(1, "Hello", 'inactive')
select title, status+0 from Posts;
Let there is a model Item with attributes id, name and specific record in DB with name 'other'.
How to get in single SQL query an ActiveRecord::Relation object with items sorted in way that other item is at last position?
As temporarily solution I used Item.where.not(name: 'other') + Item.where(name: 'other') but it results in 2 queries.
NB: it's not a real code but an extremely simplified example.
Perhaps something like below will help - we read all Items, and use slice to remove the Item with name equal to other, and append it back to array at the end.
a = Item.all.tap do |arr|
arr << arr.slice!(arr.index { |t| t.name == "other"})
end
if i get you right then you're looking for a SQL query, right? Then you could try something like that:
select id, name
from (
select id, name, decode(name, 'other', 999, 1) as sort
from items
)
order by sort
I have no database right now to test the statement. But I think you get the idea.
Good luck
Resolved with adding class method to Item model:
def self.sorted
find_by_sql("SELECT * FROM items ORDER BY (CASE name WHEN 'other' THEN 0 ELSE 1 END) DESC, id ASC")
end
I have three models: Video, Tag, and Tagging. Tags have many videos through taggings.
I have successfully added a scope to Tag that includes the video count for each tag as an extra attribute on the returned records:
class Tag
def self.include_video_count
t = Tag.arel_table
v = Video.arel_table
joins(:videos).select([t[Arel.star], v[:id].count.as("video_count")]).group(t[:id])
end
end
Tag.include_video_count.first.video_count
# => "42"
The problem is that the count attribute is being returned as a string instead of an integer.
Is there some Arel method I am missing that specifies what type the value should be returned as?
Apparently it depends on the type of the column that the database returns (which makes sense!)
Postgres for example returns the count as a string.
https://github.com/rails/rails/issues/3647
So... basically.. you're outa luck (as am I ;)
Here is a code I am trying to understand and the confusing part for now is the :product_id in the code, specially the ":" part of ":product_id"
My question is how should we know we should use that ":" ?
def up
# replace multiple items for a single product in a cart with a single item
Cart.all.each do |cart|
# count the number of each product in the cart
sums = cart.line_items.group(:product_id).sum(:quantity)
sums.each do |product_id, quantity|
if quantity > 1
# remove individual items
cart.line_items.where(product_id: product_id).delete_all
# replace with a single item
item = cart.line_items.build(product_id: product_id)
item.quantity = quantity
item.save!
end
end
end
end
Symbols:
are basically string constants
are created once. i.e. :product_id will be the same object whenever you use it. Hence they save memory. On the other hand if you write "product_id" multiple times, you are basically creating as many string objects
cannot take the benefit of Reg-ex and interpolation(mostly) unless you use a to_s method on a symbol
In a nutshell, use symbols for short string constants which you don't need to process or modify.
Eg: Symbols are great for keys in Hashes etc. Get it?
Symbols are just pointers to an object containing its name while strings are always different objects.
If you are going to repeat a name many times in your code then use one symbol which is the equivalent of using just one object.
For example if you use the string "France" a 100 times in your code, you would prefer to use :France. The advantage is that in the first case you would instantiate a 100 objects and in the second case just one.
In your example, maybe you are getting confused because product_id: product_id is a Hash represented in JSON style. This would be the equivalent of :product_id => product_id