Change a Date-fields data before formated by model? - ruby-on-rails

I have a model, lets call it Posts.
Posts has a Date-field referring to a date other that created_on, updated_on e tc,
Lets call this field custom_date.
When a user creates a post and inputs the custom_date field the POST will look like
params[:post][:custom_date] = "04/12/2013 01:01"
this is referring to day/month/year hour:minute.
When I call
#post= Post.new(post_params)
And then inspect #post the custom_date have been reformated to 2013-12-04 , without the hour and minute. I guess the format is the datebase (correct me if im wrong) way of storing the Date.
But, I want to get the hour and minute in here. So I figured that I could make this happen in a clean way in the model file:
private
def fix_dates_from_input
self.start_date = DateTime.parse(self.custom_date) unless start_date.custom_date?
render text: self.inspect
end
And call this before validation:
before_validation :fix_dates_from_input
How ever, it seems the date is formated when it gets to the model, so that I can't change it there (becouse I dont have the minute or hour data).
I was hoping that I could do this outside of the controller,
Is there another way?

It is likely that your database is only storing the date and not the datetime.
Check the db/schema.rb file to see if this is the problem. If this is the issue, it will say something like:
create_table :posts do |t|
...
t.date :custom_date
...
end
When it should look like this:
create_table :posts do |t|
...
t.datetime :custom_date
...
end
To fix this you will either want to go back and fix the original migration (if it is a new project), or if you do not have the luxury of rebuilding your database from scratch, create a new migration to fix the problem:
From command line:
rails g migration change_custom_date_type
Then edit that file:
class ChangeCustomDateType < ActiveRecord::Migration
def change
change_column :posts, :custom_date, :datetime
end
end

Related

Show time when articles were edited/created - rails

Is it possible to add the time when a certain article was edited or created through the administration framework, using rails?
By adding the time, I mean when some user reads that article on the website, he can also read the last time it was edited or created.
I know I can add it manually, but I want to make it automatic.
Thank you!
By default, every Rails generated model includes timestamps called created_at and updated_at which correspond to when the record was initially created (added to the DB) and last updated.
As long as your migration to create the model has the following line, then the timestamp functionality will be enabled
class CreateProducts < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
# ...
t.timestamps
end
end
end
Resources:
http://api.rubyonrails.org/classes/ActiveRecord/Timestamp.html
http://guides.rubyonrails.org/active_record_migrations.html

How should I use Rails to index and query a join table?

I have a ruby on Rails 4 app, using devise and with a User model and a Deal model.
I am creating a user_deals table for has_many/has_many relationship between User and Deal.
Here is the migration
class CreateUserDeals < ActiveRecord::Migration
def change
create_table :user_deals do |t|
t.belongs_to :user
t.belongs_to :deal
t.integer :nb_views
t.timestamps
end
end
end
When a user load a Deal (for example Deal id= 4), I use a method called show
controllers/deal.rb
#for the view of the Deal page
def show
end
In the view of this Deal id=4 page, I need to display the nb of views of the Devise's current_user inside the Deal page the user is currently on.
deal/show.html
here is the nb of views of user: <% current_user.#{deal_id}.nb_views%>
Lets' say I have 10M+ user_deals lines, I wanted to know if I should use an index
add_index :user_deals, :user_id
add_index :user_deals, :deal_id
or maybe
add_index(:deals, [:user_id, deal_id])
Indeed in other situations I would have said Yes, but here I don't know how Rails works behind the scenes. It feels as if Rails is aware of what to do without me needing to speed up the process,...as if when Rails loads this view that there is no SQL query (such as 'find the nb of views WHERe user_id= x and deal_id= Y')....because I'm using just for the current_user who is logged-in (via devise's current_user) and for deal_id Rails knows it as we are on the very page of this deal (show page) so I just pass it as a parameter.
So do I need an index to speed it up or not?
Your question on indexes is a good one. Rails does generate SQL* to do its magic so the normal rules for optimising databases apply.
The magic of devise only extends to the current_user. It fetches their details with a SQL query which is efficient because the user table created by devise has helpful indexes on it by default. But these aren't the indexes you'll need.
Firstly, there's a neater more idiomatic way to do what you're after
class CreateUserDeals < ActiveRecord::Migration
def change
create_join_table :users, :deals do |t|
t.integer :nb_views
t.index [:user_id, :deal_id]
t.index [:deal_id, :user_id]
t.timestamps
end
end
end
You'll notice that migration included two indexes. If you never expect to create a view of all users for a given deal then you won't need the second of those indexes. However, as #chiptuned says indexing each foreign key is nearly always the right call. An index on an integer costs few write resources but pays out big savings on read. It's a very low cost default defensive position to take.
You'll have a better time and things will feel clearer if you put your data fetching logic in the controller. Also, you're showing a deal so it will feel right to make that rather than current_user the centre of your data fetch.
You can actually do this query without using the through association because you can do it without touching the users table. (You'll likely want that through association for other circumstances though.)
Just has_many :user_deals will do the job for this.
To best take advantage of the database engine and do this in one query your controller can look like this:
def show
#deal = Deal.includes(:user_deals)
.joins(:user_deals)
.where("user_deals.user_id = ?", current_user.id)
.find(params["deal_id"])
end
Then in your view...
I can get info about the deal: <%= #deal.description %>
And thanks to the includes I can get user nb_views without a separate SQL query:
<%= #deal.user_deals.nb_views %>
* If you want to see what SQL rails is magically generating just put .to_sql on the end. e.g. sql_string = current_user.deals.to_sql or #deal.to_sql
Yes, you should use an index to speed up the querying of the user_deals records. Definitely at least on user_id, but probably both [:user_id, :deal_id] as you stated.
As for why you don't see a SQL query...
First off, your code in the view appears to be incorrect. Assuming you have set up a has_many :deals, through: :user_deals association on your User class, it should be something like:
here is the nb of views of user: <%= current_user.deals.find(deal_id).nb_views %>
If you see the right number showing up for nb_views, then a query should be made when the view is rendered unless current_user.deals is already being loaded earlier in the processing or you've got some kind of caching going on.
If Rails is "aware", there is some kind of reason behind it which you should figure out. Expected base Rails behavior is to have a SQL query issued there.
Is a cleaner way of indexing other tables not:
class CreateUserDeals < ActiveRecord::Migration
def change
create_table :user_deals do |t|
t.references :user
t.references :deal
t.integer :nb_views
t.timestamps
end
end
end

Rails set default DateTime to now

In my app I have teams and each team has a game time every week. I want the game times to be set to 'now' as a default. My table is set up like so
create_table "teams", force: true do |t|
t.datetime "wk1_time"
end
I created a migration and it looks like this:
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column :teams, :wk1_time, :default => DateTime.now
end
end
When I run rake db:migrate I get an error. Is my syntax wrong or am I missing something else?
Since Rails 5 you can make a migration like this:
change_column_default :users, :wk1_time, -> { 'CURRENT_TIMESTAMP' }
In my mind this is the best option because it not database specific answer.
Yes, you are missing the type :
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column :teams, :wk1_time, :datetime, :default => DateTime.now
end
end
But, you need the below not the above one, because you just want to change the default.
class ChangeDateTimeDefault < ActiveRecord::Migration
def change
change_column_default :teams, :wk1_time, DateTime.now
end
end
But none of these are correct approach for your task. The reason is DateTime.now will be evaluated based upon when you ran the migration, instead when the record is created. You need look to into this answer to know how to set the default time.
EDIT: For Rails 5+ there are better answers, like this one: https://stackoverflow.com/a/55357711/252799, though the below still works.
The way I found, was to do a migration on an existing datetime column, like this:
#migration
execute("ALTER TABLE teams ALTER COLUMN wk1_time SET DEFAULT CURRENT_TIMESTAMP")
that produces a schema.rb entry shown like this:
#schema.rb
t.datetime "wk1_time", default: "now()", null: false
The "now()" is a string sent to postgresql and evaluated at runtime, upon each insert.
You're going to run into problems settings the default date time in the migration. This is because DateTime.now will be evaluated based upon when the migrate runs, not when the record is created!
To fix that you'll need to create an ActiveRecord callback in order to set wk1_time like so:
before_create :set_default_wk1_datetime
def set_default_wk1_datetime
self.wk1_time = DateTime.now
end
for Postgresql :
add_column :users, :msgs_seen_at, 'TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP'
but you'll have to use user.reload after user = User.create in order to "see" msgs_seen_at

Rails calling Models in the Migration

I have a migration that creates a table, and I need to access the model for that table to create another table. The migration seems to not recognized that the original table has been created, so to make sure I put a debugger in my code and when I get to the model call, it says User(Table doesn't exist) even though in mysql I see it being created.
Looks like migrations can't see the current state of the database, any ideas how to get around that?
Just to be more specific about my question:
I'm trying to use Archivist to create archive of my current User table so I have
class ArchivedHeuristicReviewsTable < ActiveRecord::Migration
def self.up
create_table "users" do |t|
t.string "name"
...
end
debugger
Archivist.update User
end
def self.down
drop_table :users
drop_table :archived_users
end
end
the Archivist, doesn't create the archived_user table, so when I stopped at debugger and did User, I got User(Table doesn't exist).
I even tried Archivist call in a newer migration, so to make sure User creation is all done, but it still didn't recognize the user table.
Any ideas?
This should do the trick:
User.connection.schema_cache.clear!
User.reset_column_information

Migrating Data in a Rails Migration

Let's say you have "lineitems" and you used to define a "make" off of a line_item.
Eventually you realize that a make should probably be on its own model, so you create a Make model.
You then want to remove the make column off of the line_items table but for every line_item with a make you want to find_or_create_by(line_item.make).
How would I effectively do this in a rails migration? I'm pretty sure I can just run some simple find_or_create_by for each line_item but I'm worried about fallback support so I was just posting this here for any tips/advice/right direction.
Thanks!
I guess you should check that the Make.count is equal to the total unique makes in lineitems before removing the column, and raise an error if it does not. As migrations are transactional, if it blows up, the schema isn't changed and the migration isn't marked as executed. Therefore, you could do something like this:
class CreateMakesAndMigrateFromLineItems < ActiveRecord::Migration
def self.up
create_table :makes do |t|
t.string :name
…
t.timestamps
end
makes = LineItem.all.collect(:&make).uniq
makes.each { |make| Make.find_or_create_by_name make }
Make.count == makes.length ? remove_column(:line_items, :make) : raise "Boom!"
end
def self.down
# You'll want to put logic here to take you back to how things were before. Just in case!
drop_table :makes
add_column :line_items, :make
end
end
You can put regular ruby code in your migration, so you can create the new table, run some code across the old model moving the data into the new model, and then delete the columns from the original model. This is even reversible so your migration will still work in both directions.
So for your situation, create the Make table and add a make_id to the lineitem. Then for each line item, find_or_create with the make column on lineitem, setting the returned id to the new make_id on lineitem. When you are done remove the old make column from the lineitem table.

Resources