Update old record instead of creating a new one? - ruby-on-rails

I have the following table:
event_id value created_at updated_at
1 15.2 2014/01/01 00:00 2014/01/01 00:00
2 15.5 2014/01/01 00:10 2014/01/01 00:10
3 15.9 2014/01/01 00:20 2014/01/01 00:20
However, if a new Event has same value as the previous (as in newest in time) Event, then the previous Event should have "updated_at" set to current time and no new Event should be created.
In the example above, if I do Event.new(:value => 15.9), then Event with id 3 should have its updated_at set to current time - and that should be the only change.
Any suggestions on how to accomplish this? I have tried fiddling with Active Record callbacks, but fail when aborting creation (using rollback). It is of course possible to solve using a special "constructor" method, but I'd like to avoid that.

Event.where(value: params[:value]).first_or_create.touch
or in event.rb
before_save :update_if_existing
private
def update_if_existing
if event = Event.find_by(value: value)
event.touch # updates the updated_at timestamp if the existing event
false # prevents the current event from being inserted into the db
end
end

Event.find_or_create_by_value(params[:value]).touch
This method will find event by value or create one if event with this value doesn't exist. Touch method will update updated_at timestamp for this record.

You can do this way, let's say #event is the object and before saving you want to check the value
unless #event.value == Event.last.value
#event.save
else
Event.last.update_attributes(:updated_at => DateTime.now)
end
or you can do this in a single line using ternary operator
(#event.value == Event.last.value) ? (Event.last.update_attributes(:updated_at => DateTime.now)) : (#event.save)

Since your definition of Event is that no two events should have the same value - you should add a unique index on that field, which will also make any such operations much quicker.
Actually, since your event is not defined by its id but by its value, consider changing its primary key to value:
create_table(:event, :primary_key => 'value') do |t|
t.column :userID, :decimal, :null => false
...
end
class Event
set_primary_key :value
...
end
Now you can simply do:
Event.find_or_create_by_value(params[:value]).touch

Related

Active Record specify a value select out from where() in update_all

I'd like to update multiple rows with update_all, but I need to use the existed value in one column selected from where(), how can I specify the value in update_all, it looks like this:
# == Schema Information
#
# Table name: records
#
# id :integer not null, primary key
# name :string
# game_id :integer
# score :integer
# date :date
Record.where(:name => "mike", :game_id => 1).update_all(score: "here I want to take out the score out and do some modifications and then store back")
Thanks a lot.
You must use SQL code to update in the manner which you want. Note that there is no way to use Ruby code to directly manipulate the score. You could create a database function if needed.
# Increment each score by a given amount
increment_amount = 10
Record.where(:name => "mike", :game_id => 1).update_all("score=score + #{increment_amount}")
That's not really what update_all is used for. update_all generally sets the value of every instance of a certain model to one value. You could not use update_all and instead do something like
Record.where(:name => 'mike', :game_id => 1).each do |record|
# record.score = record.score * 3 + 1
record.save!
end
Where the comment is just an example.

Rails 2 + SQL server: How to make table that is supposed to have multiple timestamps?

Hi I am currently working on a Rails 2 project that uses Microsoft SQL Server. I am about to implement the delayed_job gem, which allows background processes. In order to do this, I must create a table that would look like this in a migration:
class CreateDelayedJobs < ActiveRecord::Migration
def self.up
create_table :delayed_jobs, :force => true do |table|
table.integer :priority, :default => 0 # Allows some jobs to jump to the front of the queue
table.integer :attempts, :default => 0 # Provides for retries, but still fail eventually.
table.text :handler # YAML-encoded string of the object that will do work
table.text :last_error # reason for last failure (See Note below)
table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
table.datetime :locked_at # Set when a client is working on this object
table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
table.string :locked_by # Who is working on this object (if locked)
table.timestamps
end
add_index :delayed_jobs, [:priority, :run_at], :name => 'delayed_jobs_priority'
end
def self.down
drop_table :delayed_jobs
end
end
Notice that there are 3 datetime columns. However, I must do this in pure SQL using SQL Server syntax. According to W3 schools:
timestamp Stores a unique number that gets updated every time a row gets created or modified. The timestamp value is based upon an internal clock and does not correspond to real time. Each table may have only one timestamp variable
CREATE TABLE delayed_jobs
{
id uniqueidentifier,
priority int,
attempts int,
handler text,
last_error text,
run_at timestamp,
locked_at timestamp,
failed_at timestamp,
locked_by varchar(255)
};
How can I add go around this single timestamp limitation?
How do I add indexes?
Just a heads-up, you're gonna get nailed for mentioning W3 Schools here. :-) It's an evil site. Anyway, you can have as many timestamp columns as you need. They can hold the system time or any other timestamp you want. There are no limitations.
For the indexes, use:
CREATE INDEX index_name
ON table_name (column_name)
OR
CREATE UNIQUE INDEX index_name
ON table_name (column_name)

Rails cannot write to database field

I am trying to do a very simple update on a field that does not have any validation whatsoever. However, the update always fails. Here is what the code looks like:
# model
class Event < ActiveRecord::Base
attr_accessible :start_time
..
end
# migration
class CreateEvents < ActiveRecord::Migration
def change
t.datetime :start_time
end
end
# console
Event.first.update_attribute(:start_time, "02:00")
The query that was run in the Rails log does not even include the start_time attribute!
(0.2ms) BEGIN
(4.5ms) UPDATE events SET updated_at =
'2012-07-24 19:51:33', repeat_days = '--- \n- wed\n- sat\n- sun\n',
event_date_list = '--- []\n\n' WHERE events.id = 3763
(5.5ms) COMMIT
I cannot begin to make sense of this. Can anyone help me understand the root cause of this problem?
You are a passing it a string, not a Date, Time, or Datetime object.
It looks like you just want to store the time, not the date attached. But maybe you meant to attach a date as well. If you want to store the date as well, look up the Datetime class.
If you want to store just the time (hours, minutes, and seconds), then I would suggest you change your start_time field to be an integer, and store the seconds: 2.hours or 2.hours + 4.minutes + 6.seconds.
You can convert that easily in to time again.

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.

Looking for best way to retrieve business hours from database

I'm using Ruby on Rails and I'm storing business hours like this:
CREATE TABLE "business_hours" (
"id" integer NOT NULL PRIMARY KEY,
"business_id" integer NOT NULL FOREIGN KEY REFERENCES "businesses",
"day" integer NOT NULL,
"open_time" time,
"close_time" time)
(which came from the thread at:
Storing Business Hours in a Database )
Now I want to pull the hours out for each day of the week and display them, and I'm trying to find the best (or at least a good) way.
Should I just have a helper method that loops through getting the days (from 0..6) for a given business_id and assign it to a variable for the associated day? I feel like there must be a better way -- with an array, or something, but it's hurting my head thinking about it, because I also have a form of 'select's where any of the hours for a given business can be updated at once.
Thanks for any guidance!
Use the enum column plugin to declare the day field as a enum field.
class BusinessHours < ActiveRecord::Migration
def self.up
create_table :business_hours do |t|
t.integer :business_id, :null => false
t.enum :day, :limit =>[:sun, :mon, :tue, :wed, :thu, :fri, :sat], :nill => false
t.time :open_time, :null => false
t.time :close_time, :null => false
end
end
def self.down
drop_table :business_hours
end
end
Now when you do find on the BusinessHour model you will get the day as a string.
b = BusinessHour.find_by_business_id(2).first
p b.day.to_s.camelize #prints Sun/Mon/Tue etc.
You can use the enum_select and enum_radio form helpers to create list box/radio button group for the enum group:
Since the number of days in a week really is fixed, you can join the table 6 times (plus the original) and do a query for a single row. I'd probably just do a single query and loop through the rows though.
Have you considered serializing the business hours? Using serialization you are essentially storing objects in the database.
class BusinessHour < ActiveRecord::Base
serialize :hours
...
end
BusinessHour.create :business => #business, :hours =>
{:mon => [mon_start_time, mon_end_time], :wed => [wed_start_time, wed_end_time],
...}
Personally I would go with the bitwise approach described in linked question. All you really need to do to make it work is write new accessor methods.
It would be easier to find the business and use the associations to retrieve the business_hours rows.
Try this in your view
<% #business.business_hours.each do |hrs| %>
<%= hrs.day_name %>: Open-<%= hrs.open_time %> Close-<%= hrs.close_time %>
<%- end -%>
In your business_hour.rb model file, create a default scope to make sure the days are always listed in order. You can also create the day_name method to make it easier to display the day.
default_scope :order => 'day ASC'
def day_name
case self.day
when 0 then "Sun"
when 1 then "Mon"
...
end
end

Resources