get last insert id when using Activerecord - ruby

For Sqilte3 C API, I would use sqlite3_last_insert_rowid. How to get this id when using ActiveRecord after insert a new record? I use following way to insert a new record :
Section.new |s|
s.a = 1
s.b = 2
#I expected the return value of save to be the last_insert_id, but it is NOT
s.save
end

'save' method returns true or false based on if saving was successful or not
try smth like:
new_section = Section.new do |s|
s.a = 1
s.b = 2
s.save
end
p new_section
it should print all fields you set manually plus any automatically filled values, including last_insert_id being usually the record 'id'

Assuming you've used the ActiveRecord default field name to hold the primary key, then s.id will hold the id of the record you've just saved.

Related

Ruby - How to filter SQLite rows based on column conditions

I have a variable session[:pet_profile_tags] that returns an array like ["adult", "activity_low", "overweight"].
I then have an SQLite database, called balto.db, which contains the table pet_tips. That table contains 2 columns, "ID" (Integer) and "C1_inclusion" (VARCHAR).
For each row of the pet_tips table, I need to check if the value contained in the C1_inclusion column contains one of the values of the session[:pet_profile_tags] array variable. If that is the case, I need to check the row's ID and store it inside another array variable, named pet_tips.
I tried the below code but I am getting the following error: TypeError - no implicit conversion of String into Integer: index.rb:428:in '[]' , line 428 being if (session[:pet_profile_tags].include?(row["C1_inclusion"].to_s))
# Assign pet tips
pet_tips = []
# Query the pet_tips table
db = SQLite3::Database.open "balto.db"
rows = db.execute("SELECT * FROM pet_tips")
# Iterate through each row of the table
rows.each do |row|
# Check if the row matches the C1_inclusion column
if (session[:pet_profile_tags].include?(row["C1_inclusion"].to_s))
# If the row matches, add the ID to the pet_tips array
pet_tips << row["ID"]
end
end
session[:pet_tips] = pet_tips
db.close
I have been stuck for hours, any help would be really appreciated!
First I tried returning the value of the session[:pet_profile_tags] variable to make sure I was getting the correct array. Then, I made sure to check that the different column and variable names where correctly referenced in my code.
Error
Your error is here: row["C1_inclusion"]
row is an Array of values in column order e.g. [1,"adult"].
Array#[] expects an Integer (index) but you are calling it with a String ("C1_inclusion")
Solutions
To solve this you can
Option 1:
Use Integer indexes based on column order
if (session[:pet_profile_tags].include?(row[1]))
pet_tips << row[0]
end
Option 2: convert the row to Hash:
Always:
db = SQLite3::Database.open "balto.db"
db.results_as_hash
rows = db.execute("SELECT * FROM pet_tips")
rows.each do |row|
if (session[:pet_profile_tags].include?(row["C1_inclusion"]))
Just for this loop:
rows.each_hash do |row|
if (session[:pet_profile_tags].include?(row["C1_inclusion"]))
Option 3: Query just the data you want.
# Query the pet_tips table
db = SQLite3::Database.open "balto.db"
session[:pet_tips] = db.prepare("SELECT ID FROM pet_tips WHERE pet_tips.C1_inclusion IN (?)")
.execute!(session[:pet_profile_tags].map {|s| "'#{s}'"}.join(","))
.flatten
db.close
This uses session[:pet_profile_tags] as a WHERE condition against pet_tips.C1_inclusion and assumes you have control over this variable (e.g. does not perform escaping)

Using sqlite3 with Ruby, is there a way to read each row as a hash instead of array?

I am using sqlite3 as follows:
db = SQLite3::Database.open "data.db"
stm = db.prepare "SELECT * FROM COMPANIES"
rs = stm.execute
while (row = rs.next) do
name = row[1]
that is, the row is an array instead of a hash. Is there a way to read it as a hash, so that
row[:name]
row[:address]
is the data. That's because in the future, if the table has a different order of columns, row[3] may become row[5], so row[:address] is much more definite.
There's results_as_hash setter to get this behavior:
db = SQLite3::Databse.open 'data.db'
db.results_as_hash = true
# ...

Ruby Active Record join with condition

I have a column in the Tooltype table called "deleted" which can be true or false. I only want the records which are false. I somehow only manage to check for the second table (toolunits) but not for the first (tooltype). So this WOULD work if I had a column "deleted" in my toolunits table:
obj = Tooltype.joins(:toolunits).where(toolunits: {deleted: false}).distinct
But this does not (see third line):
get '/api/tooltypes' do
if params['selector']
obj = Tooltype.joins(:toolunits).where(tooltype: {deleted: false}).distinct
else
obj = Tooltype.joins(:toolunits).distinct
end
obj.get_list() do |q|
if params['selector']
q.where(deleted: false)
end
end.serialize.first
end
How can I use the condition on the first table?
I'm thinking about two ways
Tooltype.joins(:toolunits).where(Tooltype.table_name => {deleted: false})
Tooltype.joins(:toolunits).where('tooltypes.deleted = ?', false)

appending to rails field value

I need to find and update a number of records in a Rails 3.2, Ruby 2 application. The following code successfully finds the records I want. What I need to do though is add " x" (including the space) to the email address of every user and I can't figure out how to do it.
This finds the records
User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
.where("accounts.partner_id IN (?)", [23,50])
.where("users.staff = '0'")
.where("users.admin = '0'")
.where("users.api_user = '0'")
.where("users.partner_id is null")
.update_all(email: :email.to_s << " X")
but it's the last line I'm having problems with. Is this possible, or do I need to find the records another way?
The update_all method updates a collection of records, but unless you write your own SQL expression, it can only set one value. For example, if you wanted to overwrite all the email addresses with "X", you could do it easily:
User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
# ...other scopes...
.update_all(email: "X")
In your case, what you really need to do is make individual updates to all these records. One way to do it is to find the records, then loop over them and update them one at a time:
users_to_update = User.joins(:account)
.where("users.account_id NOT IN (?)", [1955, 3083, 3869])
.where("accounts.partner_id IN (?)", [23,50])
.where("users.staff = '0'")
.where("users.admin = '0'")
.where("users.api_user = '0'")
.where("users.partner_id is null")
users_to_update.each do |user|
user.update_attribute(:email, "#{user.email} X")
end
Another solution would be to use a SQL expression with update_all, as in Zoran's answer.
Try writing the last line like so:
.update_all("email = email || ' X'")
This uses SQL's string concatenation operator to append the X to the end of the emails.
Hope that helps!

Using Rails Update to Append to a Text Column in Postgresql

Thanks in advance for any help on this one.
I have a model in rails that includes a postgresql text column.
I want to append (i.e. mycolumn = mycolumn || newdata) data to the existing column. The sql I want to generate would look like:
update MyOjbs set mycolumn = mycolumn || newdata where id = 12;
I would rather not select the data, update the attribute and then write the new data back to the database. The text column could grow relatively large and I'd rather not read that data if I don't need to.
I DO NOT want to do this:
#myinstvar = MyObj.select(:mycolumn).find(12)
newdata = #myinstvar.mycolumn.to_s + newdata
#myinstvar.update_attribute(:mycolumn, newdata)
Do I need to do a raw sql transaction to accomplish this?
I think you could solve this problem directly writing your query using the arel gem, that's already provided with rails.
Given that you have these values:
column_id = 12
newdata = "a custom string"
you can update the table this way:
# Initialize the Table and UpdateManager objects
table = MyOjbs.arel_table
update_manager = Arel::UpdateManager.new Arel::Table.engine
update_manager.table(table)
# Compose the concat() function
concat = Arel::Nodes::NamedFunction.new 'concat', [table[:mycolumn], new_data]
concat_sql = Arel::Nodes::SqlLiteral.new concat.to_sql
# Set up the update manager
update_manager.set(
[[table[:mycolumn], concat_sql]]
).where(
table[:id].eq(column_id)
)
# Execute the update
ActiveRecord::Base.connection.execute update_manager.to_sql
This will generate a SQL string like this one:
UPDATE "MyObjs" SET "mycolumn" = concat("MyObjs"."mycolumn", 'a custom string') WHERE "MyObjs"."id" = 12"

Resources