I am trying to populate a new table from an existing database but my method does not seem to be working properly. Below is my code.
class CreateEmployees < ActiveRecord::Migration
def change
create_table :employees do |t|
t.string :first_name, null: false
t.string :last_name, null: false
t.string :email, null: false
t.timestamps
end
Sale.find_each do |sale|
unless Employee.exists?(sale.employee)
puts "Employee #{sale.employee} created!"
else
puts "Employee #{sale.employee} already existed!"
end
employee_info = sale.employee.split
Employee.find_or_create_by(first_name: employee_info[0], last_name: employee_info[1], email:employee_info[2])
end
end
end
What I have is a main database called sales that with a field that contains employee. In that field you will find a string entry as so: "Mary Higgins higgins#korning.com".
Basically the sales database contains four distinct employees but the employees are listed many times. What I'm trying to do is to create four unique rows. I thought the code above would work but something seems to be off with my logic. When I run the above code it, goes through the n amount of rows and creates the Employee object so, essentially the unless statement never results to true for some reason. Could the problem lie in the .find_each method. Would a .each suffice? I don't know if any more information would need to be provided with my database but if its needed I'll supply more details.
sale.employee is a string eg "Mary Higgins higgins#korning.com"
exists? excepts a hash with the conditions like Employee.exists?(:email => "higgins#korning.com"). If you pass a string like you did, first, it converts the string to an integer then tries to find the record with that id which in your case will be 0 and because of that it always returns false.
I would change the find_each loop like this:
Sale.find_each do |sale|
employee_info = sale.employee.split
employee = Employee.find_or_create_by(first_name: employee_info[0], last_name: employee_info[1], email:employee_info[2])
if employee.new_record?
puts "Employee #{sale.employee} created!"
else
puts "Employee #{sale.employee} already existed!"
end
end
Related
I have a column that is supposed to be a string. In schema.rb it looks something like this:
create_table "users", force: :cascade do |t|
t.string "login_token", default: "xxxxx", null: false
end
But if I try to update the column, the DB accepts integers and automatically converts them to strings for some reason.
user = User.first.update(login_token: 1)
#=> true
user.login_token
#=> "1"
Why is this, and is it possible to add any restrictions to the DB or validations in Rails to prevent this kind of typecasting?
Works the other way around too. If you have an integer column and pass a string, rails tries to convert it. Very useful when you, say, create a record from an html form (most everything comes as a string from the browser).
user_params # => { "age" => "20" }
u = User.create(user_params)
u.age # => 20
It's a feature/convention. I wouldn't fight it, if I were you.
Ok, so I need help with datetime database fields.
Let's say my table is called "events" and has a datetime field named "starts_at". I have confirmed this in my schema.rb file (technically I am using Postgres):
create_table "events", force: true do |t|
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "starts_at
...
end
In my event.rb model, I have a validation to make sure the starts_at datetime is set:
attr_accessor :starts_at
validates :starts_at, presence: true
I'm testing this via the rails console and I can't get it to save any value, let alone create any validation errors. What gives? For example:
e = Event.new
e.name = 'Post 1'
e.starts_at = DateTime.now.utc
e.save
It appears to save, but there is no validation error, no mention of "starts_at" in the displayed query. Starts_at is nil in the database. My schema defines it as a "datetime" field but it ignores my DateTime variable. I thought, ok, maybe it is technically a string field and rails doesn't auto-convert for me:
e = Event.new
e.name = 'Post 2'
e.starts_at = DateTime.now.utc.to_s
e.save
Same thing. Thinking that the resulting format is being rejected in Postgres, I try this:
e = Event.new
e.name = 'Post 3'
e.starts_at = DateTime.now.utc.strftime('%Y-%m-%d %H:%M:%S')
e.save
I thought it might work with:
e.starts_at = Time.now.utc
Or, for Unix timestamp integer style:
e.starts_at = DateTime.now.utc.to_i
Nope, nothing works. What am I doing wrong?
Some questions:
How do I get a datetime field to accept my input -- any input! -- and actually save it to the database? Do I need to know which time format is ultimately being used by the database type (Postgres, MySQL, etc) and adjust accordingly? i.e. How does database agnosticism apply?
How can I update my validation to check if the database actually accepted my input? It's counterintuitive that I am validating the presence of my starts_at variable, but it will in fact allow it to be saved as nil.
Remove this line:
attr_accessor :starts_at
It masks the original setter that comes with Rails and sets a instance variable instead.
If there is a column (like the starts_at column here) in the database then there is no need to define a getter oder setter method on your own.
Probable issue is that Rails is protecting you from mass assignment of variables.
You need to remove attr_accessor on that columns that are saved as NULL in the database.
Explained really well here - What is attr_accessor in Ruby? and Difference between attr_accessor and attr_accessible
I'm trying to write a migration and it looks something like this:
class AddStatusToWorks < ActiveRecord::Migration
def self.up
change_table :works do |t|
t.string :status
end
end
def self.down
change_table :works do |t|
t.remove :status
end
end
end
Thing is, I want to set different default values for "status" based on a boolean value that's already in the table, "complete." If complete = true, status = "complete." If not, status = "work in progress." (The reason I want a string instead of keeping complete as the boolean is because I want there to be able to be more than two possibilites for status.) Any idea how to do that? Do I just stick an if statement in there like this
change_table :works do |t|
t.string :status
if (:complete == true)
:value => "complete"
else
:value => "wip"
end
Er, so that doesn't look quite right. I googled a bit and found that you can set :default values, but that's not quite what I'm going for. Any ideas/help would be lovely. Thanks!
You don't need a default at all, you just need to add the new column and give it values. Something like this should work:
def self.up
change_table :works do |t|
t.string :status
end
Works.reset_column_information
Works.where(:complete => true).update_all(:status => 'complete')
Works.where(:complete => [false, nil]).update_all(:status => 'wip')
end
See the Migrations Guide for information on reset_column_information.
You could also do it straight in the database but you have to be careful about different boolean representations (PostgreSQL wants 't' and 'f', MySQL wants 1 and 0, SQLite wants 1 and 0 but Rails mistakenly uses 't' and 'f', ...):
t = connection.quote(true)
connection.execute(%Q{
update works
set status = case complete
when #{t} then 'complete'
else 'wip'
end
})
Remember that default values are created immediately when the record is created, and thus don't yet have values for fields w/o defaults. You'll likely want to move this sort of logic to your model. Perhaps in an ActiveRecord callback, i.e. before_validation or before_save.
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
I have an 'Account' model in Rails with its corresponding 'accounts' table in the database. If I wipe the database and start over, the 'account_id' field will always start at 1 and count up from there. I would like to change the starting number, so that, when the very first account is created in a fresh database, the 'account_id' is, say, 1000. Is there a way to do that in Rails, or do I need specialized database-dependent SQL code?
For the sake of illustration, here is a simplified version of my 'accounts' table:
create_table "accounts", :force => true do |t|
t.string "email", :null => false
t.string "crypted_password", :null => false
t.string "name", :null => false
t.boolean "email_verified", :default => false
end
for PostgreSQL:
execute("ALTER SEQUENCE accounts_id_seq START with 1000 RESTART;")
see https://www.postgresql.org/docs/current/static/sql-altersequence.html
You'll need to do some specialized database-dependent SQL to get this functionality.
If you're using MySQL, you can add the following code to your migration after the create_table code:
execute("ALTER TABLE tbl AUTO_INCREMENT = 1000")
For sqlite
sequences are stored in the table sqlite_sequence (name,seq)
Check first if the sequence already exists?
select name,seq from sqlite_sequence where name = 'accounts'
if sequence.empty?
insert into sqlite_sequence(name,seq) values('accounts', 1000);
else
update sqlite_sequence set seq = 1000 where name = 'accounts';
A pure Ruby, database-independent approach could be:
class MyModel
before_create do
self.id = [1000, (self.class.maximum(:id) || 0) + 1].max if self.id.nil?
end
end
When you're creating lots of records at once, this may not perform so well though.
Another possible concept might be to simply use a start_at variable in your model file?
Such as define a base number such as start_at = 53131 and then...
Make an accessor method (could call it "key") which adds your start_at number to your database's real ID before returning it.
And you could make a attr writer method that subtracts the start_at before saving the key, that may not even be necessary depending on your implementation.
Example in pseudo-code so bear with me.
class FakeModel
attr_accessible :name
start_at = 53121
def self.find_by_key(key)
find_by_id(key-start_at))
end
def key
(self.id+start_at)
end
end
Not sure how practical this is or if it would even work 100% but at least you wouldn't have to modify the database to handle it.
in SQL Server:
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
In my case, the development environment and the production environment are using different type of database.
This code block will run the relevant execution accordin to DB type - just put it in the relevant migration:
puts 'Migration trys to set initial account ID to adapter:' + ActiveRecord::Base.connection.adapter_name
case ActiveRecord::Base.connection.adapter_name
when 'MySQL'
execute('ALTER TABLE accounts AUTO_INCREMENT = 1000')
when 'SQLServer'
execute('DBCC CHECKIDENT (accounts, reseed, 1000)')
when 'SQLite'
begin
execute('insert into sqlite_sequence(name,seq) values(\'accounts\', 1000);')
rescue
puts 'insert error... updating'
end
execute('update sqlite_sequence set seq = 1000 where name = \'accounts\';')
else
puts "cant recognize the database"
end