ActiveRecord adding rating range in migration file - ruby-on-rails

class AddRatingToBooks < ActiveRecord::Migration
def up
add_column :books, :rating, :integer
end
def down
remove_column :books, :rating
end
I have the following snippet of code in my db/migrate/, I'm trying to add ratings to my books table, where it would be in a range from 0-100, but I'm not sure how to add that here, all i could find was querying with ranges. I'm sure it's simple I'm just not there yet.

You don't need to specify the range of integer values in your migration file. The migration file is simply used to add the database column to store the rating. This is not the place to add validations.
You should use your Book model to specify a validation that ensures your ratings fall within a certain range. Something like this:
class Book < ActiveRecord::Base
validates :rating, :inclusion => { :in => 0..100 }
end
I would highly recommend reading the Rails guides on both migrations and validations.

Probably I'm too late with the answer. But it's possible to define validation on db level with Migration Validators project: https://github.com/vprokopchuk256/mv-core
As example, in your migration:
def change
change_table :books do |t|
t.integer :rating, inclusion: 0..100
end
end
and then in your model:
class Book < ActiveRecord::Base
enforce_migration_validations
end
As result your validation will be defined both in db ( as statement inside trigger or check constraint, depending on your db) and on your model
SQL ( PostgreSQL ):
=# insert into books(rating) values(10);
INSERT 0 1
=# insert into books(rating) values(200);
ERROR: new row for relation "books" violates check constraint "chk_mv_books_rating"
Rails console:
Book.new(title: 10).valid?
=> true
Book.new(title: 200).valid?
=> false

Related

Rails updating column rows for has_one relationship

I have added iconfolio to my character model. Each character has_one :iconfolio.
character.rb
has_one :iconfolio, dependent: :destroy
accepts_nested_attributes_for :iconfolio
before_validation do
self.create_iconfolio unless iconfolio
end
Here is the migration file:
class CreateIconfolios < ActiveRecord::Migration
def change
create_table :iconfolios do |t|
t.integer :character_id
t.string :icon_url
t.timestamps null: false
end
add_index :iconfolios, :character_id
end
end
The iconfolio class:
iconfolio.rb
class Iconfolio < ActiveRecord::Base
belongs_to :character
validates :character_id, presence: true
before_create do
self.icon_url = '/assets/icon1.png'
end
end
Firstly, how do I ensure an iconfolio has been created for each character?
Secondly, how do I update all the rows in the character_id column? The character_id value is different for every iconfolio record. Updating the icon_url column can be done in the console:
Iconfolio.all.update_all(person_normal_icon_url: '/assets/icon1.png')
The easiest way to do this is to create a default template iconfolio and update_all character records as you did with Iconfolio. If your database isn't already large you can iterate through Character.all and assign them an iconfolio. Be advised this will instantiate an object per row and be much more time consuming than update_all. The benefit of instantiating them is it will not bypass your validations. Write a block in your console that iterates through each record and finds or creates an iconfolio per character like:
Character.all.find_each do |char|
if char.iconfolio.blank?
Iconfolio.create(character_id: char.id, whatever_other_params: put_here)
end
end
Then make an after_create in your Character model that creates and assigns an iconfolio for future new characters. Something like:
after_create :make_an_iconfolio
def make_an_iconfolio
Iconfolio.create(character_id: self.id, other params here)
end
As a sidenote, the Rails way to add a relationship in your create_table migration is:
def change
create_table :iconfolios do |t|
t.belongs_to :character, index: true
This just makes it more clear to you and others that it's a relationship and saves an extra line of code from indexing.

Set new column value from serialized hash in migration

I have a model which has some information which is best stored as a serialized Hash on the model, as it is unimportant to most of the app and varies from instance to instance:
class Foo < AR::Base
attr_accessible :name, :fields
serialize :fields
end
I have realised that one of the common entries in fields actually is relevant to the app, and would be better placed as an attribute (layout).
Bearing in mind that I should not, ideally, refer to models in migrations, how can I write a migration to add the layout field, and initialise it with the value currently in the fields Hash?
class AddLayoutToCardTemplates < ActiveRecord::Migration
def change
add_column :card_templates, :layout, :string, default: 'normal'
# Initialise `layout` from `fields['layout']`... how? With raw SQL?
end
end
You should not refer to models in your app folder. This doesn't mean you cannot create local model. :)
class AddLayoutToCardTemplates < ActiveRecord::Migration
class Foo < AR::Base
attr_accessible :name, :fields
serialize :fields
end
def change
add_column :card_templates, :layout, :string, default: 'normal'
Foo.all.each do |f|
f.layout = f.fields.delete(:layout)
f.save
end
end
That way your migration can use ActiveRecord goodies and yet stays time-independent, as your real model in app folder is never loaded.

How to change a reference column to be polymorphic?

My model (Bar) already has a reference column, let's call it foo_id and now I need to change foo_id to fooable_id and make it polymorphic.
I figure I have two options:
Create new reference column fooable which is polymorphic and migrate the ID's from foo_id (What would be the best way to migrate these? Could I just do Bar.each { |b| b.fooable_id = b.foo_id }?
Rename foo_id to fooable_id and add polymorphic to fooable_id. How to add polymorpic to an existing column?
1. Change the name of foo_id to fooable_id by renaming it in a migration like:
rename_column :bars, :foo_id, :fooable_id
2. and add polymorphism to it by adding the required foo_type column in a migration:
add_column :bars, :fooable_type, :string
3. and in your model:
class Bar < ActiveRecord::Base
belongs_to :fooable,
polymorphic: true
end
4. Finally seed the type of you already associated type like:
Bar.update_all(fooable_type: 'Foo')
Read Define polymorphic ActiveRecord model association!
Update for Rails >= 4.2
TLDR
add new reference
copy reference ids
remove old reference
So the migration is:
def change
add_reference :bars, :fooable, polymorphic: true
reversible do |dir|
dir.up { Bar.update_all("fooable_id = foo_id, fooable_type='Foo'") }
dir.down { Bar.update_all('foo_id = fooable_id') }
end
remove_reference :bars, :foo, index: true, foreign_key: true
end
Background
Current Rails generates references with index and foreign_key, which is a good thing.
This means that the answer of #Christian Rolle is no longer valid as after renaming foo_id it leaves a foreign_key on bars.fooable_id referencing foo.id which is invalid for other fooables.
Luckily, also the migrations evolve, so undoable migrations for references do exist.
Instead of renaming the reference id, you need to create a new reference and remove the old one.
What's new is the need to migrate the ids from the old reference to the new one.
This could be done by a
Bar.find_each { |bar| bar.update fooable_id: bar.foo_id }
but this can be very slow when there are already many relations. Bar.update_all does it on database level, which is much faster.
Of course, you should be able to roll back the migration, so when using foreign_keys the complete migration is:
def change
add_reference :bars, :fooable, polymorphic: true
reversible do |dir|
dir.up { Bar.update_all("fooable_id = foo_id, fooable_type='Foo'") }
dir.down { Bar.update_all('foo_id = fooable_id') }
end
remove_reference :bars, :foo, index: true, foreign_key: true
end
Remember that during rollback, change is processed from bottom to top, so foo_id is created before the update_all and everything is fine.
One small change I would make is to the migration:
#db/migrate/latest.rb
class Latest
def change
rename_column :bars, :foo_id, :fooable_id
add_column :bars, :fooable_type, :string, after: :id, default: 'Foo'
end
end
This would eliminate the need to do a data migration.
Update: this will work on rails 3 and up. According to the question the original base class is implied to be Foo.
In reference to your question specifically, here's what I'd do:
All the data in your Bar model is going to be stored in reference to the Bar model. This means that if you change the foo_id attribute in your model, you'll be able to just populate the bar_type attribute you need to add (as they'll all be able to reference the same model)
The way to do this is as follows:
Create migration for foo_id > fooable_id
Insert a fooable_type column
In rails console, loop through all existing records of Bar, filling the fooable_type column
First things first:
$ rails g migration ChangeFooID
#db/migrate/latest.rb
class Latest
def change
rename_column :bars, :foo_id, :fooable_id
add_column :bars, :fooable_type, :string, after: :id
end
end
This will create the various columns for you. Then you just need to be able to cycle through the records & change the type column:
rails c
Bar.find_each do |bar|
bar.update(barable_type: "Foo")
end
This will allow you to change the type of your columns, giving you the ability to associate all the current records with the respective records.
Polymorphism
You'll be able to use the Rails docs as a reference as to how to associate your models:
#app/models/foo.rb
class Foo < ActiveRecord::Base
has_many :bars, as: :barable
end
#app/models/bar.rb
class Bar < ActiveRecord::Base
belongs_to :foo, polymorphic: true
end

How to add a default AASM state to existing model

I have an existing model in rails and I want to add AASM states to it.
From my understanding, I should add a state column to my database through migrations first and then add some states to my rails model. How do I set a default state value according to a value in another column?
Am I on the right track at all?
You are on the right track. You can set the initial state for new records in the migration itself.
Either use the :default option as follows. This is most useful if every record has the exact same starting state:
# Assuming your model is named Order
class AddStateToOrders < ActiveRecord::Migration
add_column :orders, :state, :string, :default => 'new'
end
Or you can use a simple bit of ruby to set the state of each record after the column is added. More useful if the initial state of records is conditional on something.
# Still assuming your model is named Order
class AddStateToOrders < ActiveRecord::Migration
add_column :orders, :state, :string
# Loop through all the orders, find out whether it was paid and set the state accordingly
Order.all.each do |order|
if order.paid_on.blank?
order.state = 'new'
else
order.state = 'paid'
end
order.save
end
end
Peter's answer is good, but it has one defect. You'll need to write a new migration if you change default state. So,
class AddStateToOrders < ActiveRecord::Migration
def self.up
add_column :orders, :state, :string
Order.update_all(aasm_state: 'new') # it will apply just for existing records with empty state.
end
def self.down
remove_column :orders, :state
end
end

Rails 3 migrations: Adding reference column?

If I create a new rails 3 migration with (for example)
rails g migration tester title:tester user:references
, everything works fine...however if I add a column with something along the lines of:
rails g migration add_user_to_tester user:references
the reference field is not recognised. In short, the question is: how do I add a referencing column to a rails migration from the command line?
If you are using the Rails 4.x you can now generate migrations with references, like this:
rails generate migration AddUserRefToProducts user:references
like you can see on rails guides
EDIT: This is an outdated answer and should not be applied for Rails 4.x+
You don't need to add references when you can use an integer id to your referenced class.
I'd say the advantage of using references instead of a plain integer is that the model will be predefined with belongs_to and since the model is already created and will not be affected when you migrate something existing, the purpose is kind of lost.
So I would do like this instead:
rails g migration add_user_id_to_tester user_id:integer
And then manually add belongs_to :user in the Tester model
Please note that you will most likely need an index on that column too.
class AddUserReferenceToTester < ActiveRecord::Migration
def change
add_column :testers, :user_id, :integer
add_index :testers, :user_id
end
end
With the two previous steps stated above, you're still missing the foreign key constraint. This should work:
class AddUserReferenceToTester < ActiveRecord::Migration
def change
add_column :testers, :user_id, :integer, references: :users
end
end
You can use references in a change migration. This is valid Rails 3.2.13 code:
class AddUserToTester < ActiveRecord::Migration
def change
change_table :testers do |t|
t.references :user, index: true
end
end
def down
change_table :testers do |t|
t.remove :user_id
end
end
end
c.f.: http://apidock.com/rails/ActiveRecord/ConnectionAdapters/SchemaStatements/change_table
Running rails g migration AddUserRefToSponsors user:references will generate the following migration:
def change
add_reference :sponsors, :user, index: true
end
When adding a column you need to make that column an integer and if possible stick with rails conventions. So for your case I am assuming you already have a Tester and User models, and testers and users tables.
To add the foreign key you need to create an integer column with the name user_id (convention):
add_column :tester, :user_id, :integer
Then add a belongs_to to the tester model:
class Tester < ActiveRecord::Base
belongs_to :user
end
And you might also want to add an index for the foreign key (this is something the references already does for you):
add_index :tester, :user_id
That will do the trick:
rails g migration add_user_to_tester user_id:integer:index
You can add references to your model through command line in the following manner:
rails g migration add_column_to_tester user_id:integer
This will generate a migration file like :
class AddColumnToTesters < ActiveRecord::Migration
def change
add_column :testers, :user_id, :integer
end
end
This works fine every time i use it..
For Rails 4
The generator accepts column type as references (also available as belongs_to).
This migration will create a user_id column and appropriate index:
$ rails g migration AddUserRefToProducts user:references
generates:
class AddUserRefToProducts < ActiveRecord::Migration
def change
add_reference :products, :user, index: true
end
end
http://guides.rubyonrails.org/active_record_migrations.html#creating-a-standalone-migration
For Rails 3
Helper is called references (also available as belongs_to).
This migration will create a category_id column of the appropriate type. Note that you pass the model name, not the column name. Active Record adds the _id for you.
change_table :products do |t|
t.references :category
end
If you have polymorphic belongs_to associations then references will add both of the columns required:
change_table :products do |t|
t.references :attachment, :polymorphic => {:default => 'Photo'}
end
Will add an attachment_id column and a string attachment_type column with a default value of Photo.
http://guides.rubyonrails.org/v3.2.21/migrations.html#creating-a-standalone-migration

Resources