How to query on a method value? - ruby-on-rails

Consider this table:
create_table "liquor_lots", force: :cascade do |t|
t.integer "recipe_id"
t.datetime "created_at", precision: 6, null: false
t.integer "counter"
end
And the resulting model
class LiquorLot < ApplicationRecord
def lotcode
"#{recipe_id}#{created_at.strftime("%y")}#{created_at.strftime("%W")}#{created_at.strftime("%u")}"
end
def pallet_lotcode
"#{lotcode}-#{counter}"
end
end
I'd like to do the equivalent of this in SQL:
Select distinct(lotcode) from liquor_lots
I've tried this and it understandably fails because lotcode is not a column on the liquor_lots table. But I've always been advised against adding columns to store data that is derived from data in other columns.
So how do I search for those values?
For context, my lotcode actually consists of many more values concatenated together, I just limited to three in the example for readability.

As far as I know, with basic ActiveRecord you cannot do that.
ActiveRecord would have to know too much about your ruby code.
You could implement a SQL query that concatenates the relevant values by hand (see comment to your question).
Or you can query all objects (or just the relevant values using pluck()) and then work on that with standard Ruby Array/Enumerable methods (in memory). If the application is not performance-critical, happens rarely, and you do not have thousands of the liquor_lots, that would be an okay productivity-tradeoff in my eyes.
Besides storing it in an own column, you could also extract the codes in separate table and make PalletLotcode an entity of its own. LiquorLots would than belong_to a single PalletLotcode which would have_many LiquorLots. But compared to the separate column this is a rather complex operation, but makes sense if other information is to be stored on the Lotcodes.

You can try something like:
LiquorLot.where("recipe_id = :rcp_id AND created_at >= :begin_of_day AND created_at <= :end_of_day", {begin_of_day: calculate_begin_of_day, end_of_day: calculate_end_of_date, rcp_id: id})
calculate_begin_of_day and calculate_end_of_date can be implemented using Date.comercial method and Date.beginning_of_day and Date.end_of_day

Related

rails4 pluck with order and limit

In my sidebar I display the freshly created user profiles. Profile belongs_to user and user has_one_profile. I realized that I only use 3 columns from the profile table so it would be better to use pluck. I also have a link_to user_path(profile.user) in the partial, so somehow I have to tell who the user is. At the moment I'm using includes, but I don't need the whole user table. So I use to many columns both from the user and the profile tables.
How can I optimize this with pluck? I tried a few versions, but always got some error (most of the time profile.user is not defined).
My current code:
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3) if user_signed_in?
end
create_table "profiles", force: :cascade do |t|
t.integer "user_id", null: false
t.string "first_name", null: false
t.string "last_name", null: false
t.string "company", null: false
t.string "job_title", null: false
t.string "phone_number"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.string "avatar"
t.string "location"
end
Okay let's explain three different way to accomplish what you are looking for.
First of all there is a difference in includes and joins
Includes just eager load the association with all of the specified columns for associations. It does not allow you to query or select multiple columns from both table. It what joins do . It allow you to query both tables and select columns of your choice.
def set_sidebar_users
#profiles_sidebar = Profile.select("profiles.first_name,profiles.last_name,profiles.id,users.email as user_email,user_id").joins(:user).order("profile.created_at desc").limit(3) if user_signed_in?
end
It will return you the Profiles relation which has all of the columns you provided in select clause. You can get them just like you do for profile object e-g
#profiles_sidebar.first.user_email will give you user email for this profile.
This approach is best if you want to query on multiple tables or wanna select multiple columns from both table.
2.Pluck
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user).limit(3).pluck("users.email,profiles.first_name") if user_signed_in?
end
Pluck is just used to get columns from multiple associations but it does not allow you to use the power of ActiveRecord. It simply returns you the array of selected columns in same order.
like in the first example you can get the user for profile object with #profiles_sidebar.first.user But with pluck you cannot because it's just a plain array. So that's why your most of the solutions raise error profile.user is not defined
Association with selected columns.
Now this is option three. In first solution you can get multiple columns on both tables and use the power of ActiveRecord but it does not eager load the associations. So it will still cost you N+1 queries if you loop through the association on returned result like #profiles_sidebar.map(&:user)
So if you wanna use includes but want to use selected columns then you should have new association with selected columns and call that association.
e-g
In profile.rb
belongs_to :user_with_selected_column,select: "users.email,users.id"
Now you can include it in above code
def set_sidebar_users
#profiles_sidebar = Profile.order(created_at: :desc).includes(:user_with_selected_column).limit(3) if user_signed_in?
end
Now this will eager load users but will select only email and id of user.
More information can be found on
ActiveRecord includes. Specify included columns
UPDATE
As you asked about the pros for pluck so let's explain it.
As you know pluck returns you the plain array. So it does not instantiate ActiveRecord object it simply returns you the data returned from database.
So pluck is best to use where you don't need ActiveRecord Objects but just to show the returned data in tabular form.
Select returns you the relations so you can further query on it or call the model methods on it's instances.
So if we summaries it we can say
pluck for model values, select for model objects
More informations can be found at http://gavinmiller.io/2013/getting-to-know-pluck-and-select/

Creating a filter for active record in rails

Just started coding in Ruby on Rails and have managed to create the basic CRUD functionality for my app.
I can also list them all.
Now I would like to create a filter for the user to interact with.
Database Schema
create_table "nades", force: :cascade do |t|
t.string "title"
t.integer "grenade"
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "map_id"
end
The "grenade" can have a value from 1-4, corresponding to a specific grenade:
[[1,"smoke"], [2,"flash"], [3,"molotov"], [4,"he-grande"]
Now I'm trying to create a filter with 4 buttons in the view. Where you can toggle each nade on/off to show or hide them in the results.
[x]Smoke [ ]Flash [ ]Moltov [x]HE
This should only return the nades where grenade = [1,4]
After some reading it looks like scoped would be nice to use to manage this.
However I'm not sure how to make it work as I want.
Was thinking of doing something like:
scope :by_nade, -> grenade { where(grenade: grenade) if status.grenade? }
However this only allows me to get 1 specific nade type from the database.
Is it posible to send multiple parameters like:
http://localhost:3000/nades/?by_nade=1,2 ??
Or is it a better solution to my problem?
Look into creating a form with an array of options (e.g., here). That will allow you to get multiple values for the specific type of grenade.
Your scope will work with either a single id or an array of ids and using the array of options approach should yield an array that will work. In your example above it would effectively be Nade.by_grenade([1,4]). You might want to guard against the array being empty (assuming that an empty list would be a bad thing).

How do I efficiently check if an active record object has an association?

I have an application that has tens of thousands of snapshot records. A very small number of these 'snapshots' (say 1 in 1000) will have one or more 'positions' through a :has_many association.
How can I efficiently discover if a snapshot has a position without firing an active record query for each snapshot? My current solution is to add a boolean field to snapshots - if a snapshot has a position, 'has_position' is set to true. This seems a little messy since it means I have to modify the associated snapshot every time I create a position. Is there a cleaner way to handle this scenario?
create_table "snapshots", :force => true do |t|
t.datetime "created_at",
t.datetime "updated_at",
t.boolean "has_position",
end
create_table "positions", :force => true do |t|
t.integer "snapshot_id"
t.datetime "created_at",
t.datetime "updated_at",
end
What will happen if you generate the migration for positions with the reference to snapshots, the migration file will be generated with a
add_index :positions, :snapshot_id
appended to the end of it.
With an index on snapshot_id the DB will take log(n) queries to figure out whether or not a position has at least one associated record. Not as good as constant time with the boolean, but with mere tens of thousands of records it shouldn't take noticeably longer (unless you're doing this very, very frequently).
Additionally, a simple has_position boolean might be harder than you think to maintain without an index. You can set it to true on creation of an associated position, but you can't set it to false on the deletion because there might exist another one, and you'd have to do a table scan again.
If for some reason using an index is undesirable (or you really need constant time lookup), then I'd recommend using a :counter_cache column.

ActiveRecord::Base.connection.execute(..) looking for column with name of a value I am trying to insert into a table

Newbie question.
I am trying to use ActiveRecord::Base.connection.execute(..) in a ruby 3.1 app and docs I have read seem to be straight forward but for the life in me I can seem to understand why I cant get the code below to work. The error message I am getting suggests that the execute function is looking for a column with the name of one of the values I am trying to save but I dont understand why.
Firstly, my db table structure is as follows:
create_table "countries", :force => true do |t|
t.string "iso3"
t.string "iso2"
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
And the code Im playing with is as follows:
code = 'ZA'
name = 'South Africa'
ActiveRecord::Base.connection.execute("INSERT INTO countries ('iso3', 'iso2', 'name')
VALUES ('Null', #{code}, #{name})")
The error message I am getting is as follows:
SQLite3::SQLException: no such column: ZA: INSERT INTO countries ('iso3', 'iso2', 'name')
VALUES ('Null', ZA, SouthAfrica)
Where did you get the basis for this? Code of this variety is a sterling example of what not to do.
If you have ActiveRecord, then you have ActiveRecord::Model, and with that you're on the right track and pretty much done. You don't need to write raw SQL for routine things of this variety. It's not necessary, and more, it's extremely dangerous for the reasons you've just discovered. You can't just shove random things in to your query or you will end up with nothing but trouble.
What you should be doing is declaring a model and then using it:
# Typically app/models/country.rb
class Country < ActiveRecord::Base
end
To insert once you have a model is made seriously easy:
Country.create(
:code => 'ZA',
:name => 'South Africa'
)
A good ActiveRecord reference is invaluable as this facility will make your life significantly easier if you make use of it.
Within Rails you usually go about generating these automatically so that you have something rough to start with:
rails generate model country
This will take care of creating the migration file, the model file, and some unit test stubs you can fill in later.
The error is just because if the missing quotes. it should be like:
INSERT INTO countries ('iso3', 'iso2', 'name') VALUES ('Null', 'ZA', 'SouthAfrica')

How to add sequences to a migration and use them in a model?

I want to have a "Customer" Model with a normal primary key and another column to store a custom "Customer Number". In addition, I want the db to handle default Customer Numbers. I think, defining a sequence is the best way to do that. I use PostgreSQL. Have a look at my migration:
class CreateAccountsCustomers < ActiveRecord::Migration
def up
say "Creating sequenze for customer number starting at 1002"
execute 'CREATE SEQUENCE customer_no_seq START 1002;'
create_table :accounts_customers do |t|
t.string :type
t.integer :customer_no, :unique => true
t.integer :salutation, :limit => 1
t.string :cp_name_1
t.string :cp_name_2
t.string :cp_name_3
t.string :cp_name_4
t.string :name_first, :limit => 55
t.string :name_last, :limit => 55
t.timestamps
end
say "Adding NEXTVAL('customer_no_seq') to column cust_id"
execute "ALTER TABLE accounts_customers ALTER COLUMN customer_no SET DEFAULT NEXTVAL('customer_no_seq');"
end
def down
drop_table :accounts_customers
execute 'DROP SEQUENCE IF EXISTS customer_no_seq;'
end
end
If you know a better "rails-like" approach to add sequences, would be awesome to let me know.
Now, if I do something like
cust = Accounts::Customer.new
cust.save
the field customer_no is not pre filled with the next value of the sequence (should be 1002).
Do you know a good way to integrate sequences? Or is there a good plugin?
Cheers to all answers!
I have no suggestions for a more 'rails way' of handling custom sequences, but I can tell you why the customer_no field appears not to be being populated after a save.
When ActiveRecord saves a new record, the SQL statement will only return the ID of the new record, not all of its fields, you can see where this happens in the current rails source here https://github.com/rails/rails/blob/cf013a62686b5156336d57d57cb12e9e17b5d462/activerecord/lib/active_record/persistence.rb#L313
In order to see the value you will need to reload the object...
cust = Accounts::Customer.new
cust.save
cust.reload
If you always want to do this, consider adding an after_create hook in to your model class...
class Accounts::Customer < ActiveRecord::Base
after_create :reload
end
I believe that roboles answer is not correct.
I tried to implement this on my application (exactly the same env: RoR+PostgreSQL), and I found out that when save is issued on RoR with the object having empty attributes, it tries to perform an INSERT on the database mentioning that all VALUES shall be set to NULL. The problem is the way PostgreSQL handles NULLs: in this case, the new row will be created but with all values empty, i.e. the DEFAULT will be ignored. If save only wrote on the INSERT statement attributes filled on RoR, this would work fine.
In other words, and focusing only on the type and customer_no attribute mentioned above, this is the way PostgreSQL behaves:
SITUATION 1:
INSERT INTO accounts_customers (type, customer_no) VALUES (NULL, NULL);
(this is how Rails' save works)
Result: a new row with empty type and empty customer_no
SITUATION 2:
INSERT INTO accounts_customers (type) VALUES (NULL);
Result: a new row with empty type and customer_no filled with the sequence's NEXTVAL
I have a thread going on about this, check it out at:
Ruby on Rails+PostgreSQL: usage of custom sequences
I faced a similar problem, but I also put :null => false on the field hopping that it will be auto-populated with nextval.
Well, in my case AR was still trying to insert NULL if no attribute was supplied in the request, and this resulted in an exception for not-null constraint violation.
Here's my workaround. I just deleted this attribute key from #attributes and #changed_attributes and in this case postgres correctly put the expected sequence nextval.
I've put this in the model:
before_save do
if (#attributes["customer_no"].nil? || #attributes["customer_no"].to_i == 0)
#attributes.delete("customer_no")
#changed_attributes.delete("customer_no")
end
end
Rails 3.2 / Postgres 9.1
If you're using PostgreSQL, check out the gem I wrote, pg_sequencer:
https://github.com/code42/pg_sequencer
It provides a DSL for creating, dropping and altering sequences in ActiveRecord migrations.

Resources