How to add a default AASM state to existing model - ruby-on-rails

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

Related

making the default value for a migration based upon a calculation using two other columns

I'm adding a column to a model and I want the new column's default to be the sum of two of the existing columns in that table. Not sure if this is possible. Here's the (invalid) migration I tried:
class AddRoundsWithSpeaksToCases < ActiveRecord::Migration
def change
add_column :cases, :rounds_with_speaks, :integer, default: :wins + :losses
end
end
Is there a way to do this via a migration, or do I have to run rails console and update the attributes through there?
You wouldn't want to set the migration up that way because :wins + :losses is irrelevant for a new Case record. The default should be 0, since by "default" the value of the field will be 0 for new records.
Additionally, you don't have to rely on the console to run the update, you can put it in the migration:
class AddRoundsWithSpeaksToCases < ActiveRecord::Migration
def up
add_column :cases, :rounds_with_speaks, :integer, default: 0
Case.find_each(batch_size: 200) { |c| c.update_attribute!(:rounds_with_speaks, c.wins + c.losses) }
end
def down
remove_column :cases, :rounds_with_speaks
end
end
As a one time migration, to the historical records, you can do it like this:
class AddRoundsWithSpeaksToCases < ActiveRecord::Migration
def change
add_column :cases, :rounds_with_speaks, :integer
update "UPDATE cases SET rounds_with_speaks = wins+losses"
end
end
This won't affect any future records, you could write something like these as a SQL trigger/procedure..

ActiveRecord adding rating range in migration file

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

Ruby on Rails, create with through control

I'm in the following situation, taking over an existing website, I have model User which has many devices like that:
has_many :devices, :through => :credits
When I create a device it creates a credit, but some of the attributes of the credits are null. I'd like to know if there's a way to control the creation of this credit and make sure nothing is null in the credit created for the database.
Thanks in advance
Recommended:
Use default values in your credits database table. You can use a database migration to do this.
class AddDefaultValuesToCredits < ActiveRecord::Migration
def change
change_column :credits, :value1, :boolean, default: false
change_column :credits, :value2, :string, default: 'words'
# change other columns
end
end
If no explicit value is specified for value1 or value2, they'll default to false and 'words', respectively.
Alternative: You can also set default values in your Credit model.
class Credit < ActiveRecord::Base
after_initialize :set_values, unless: persisted?
# other model code
def set_values
if self.new_record?
self.value1 = false if self.value.nil? # initial boolean value
self.value2 ||= 'words' # initial string value
# other values
end
end

How to recognize migration direction (up or down) with Rails 3 style migrations (def change)?

I really like the Rails 3 style migrations, i.e. one change method being smart enough to recognize if the migrations is being installed or rolled back, so I don't have to write up and down methods mirroring each other. But I have situation that I need to skip some code when the migration is rolled back (updating counter_cache columns that I'm adding).
I looked at http://guides.rubyonrails.org/migrations.html but the examples at the end of section 5 suffer from the same problem:
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
add_column :products, :fuzz, :string
Product.reset_column_information
Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
When this migration is rolled back, the update of fuzz field is unnecessary. Is there a way to prevent it?
I tried looking into Product.column_names but since Rails is smart enough to perform migration in reverse direction, the update is executed before the column is removed. Also, when change method is defined, any up or down methods seem to be ignored. Any other ideas?
Just for future reference, for Rails 4 the best way to do this is to use reversible:
def change
# ...change code...
reversible do |dir|
dir.up do
# ...up-only code...
end
end
end
See http://guides.rubyonrails.org/migrations.html#using-reversible
In this case I think you'll have to use up and down methods as usual. Don't worry, despite the addition of change in Rails 3 those methods aren't, as far as I know, bound for the chopping block. Continue using them where necessary.
Edit: Here's an option: Override migrate.
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
add_column :products, :fuzz, :string
end
def migrate(direction)
super # Let Rails do its thing as usual...
if direction == :up # ...but then do something extra if we're going 'up.'
Product.reset_column_information
Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
end
Thoughts?
In rails 3.x you can also do this
class AddFuzzToProduct < ActiveRecord::Migration
class Product < ActiveRecord::Base
end
def change
add_column :products, :fuzz, :string
unless reverting?
# Do this only when direction is up
Product.reset_column_information
Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
end
Here's a nasty thought: #connection is the CommandRecorder when going down.
def change
add_column :products, :fuzz, :string
unless #connection.kind_of?(ActiveRecord::Migration::CommandRecorder)
Product.reset_column_information
Product.all.each { |f| f.update_attributes! :fuzz => 'fuzzy' }
end
end
Haven't tried it. Obviously you're outside the Rails API so it could break at any time.
If only the change method had a legitimate way of determining the migration direction...

How do you deal with breaking changes in a Rails migration?

Let's say I'm starting out with this model:
class Location < ActiveRecord::Base
attr_accessible :company_name, :location_name
end
Now I want to refactor one of the values into an associated model.
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :name, :null => false
t.timestamps
end
add_column :locations, :company_id, :integer, :null => false
end
def self.down
drop_table :companies
remove_column :locations, :company_id
end
end
class Location < ActiveRecord::Base
attr_accessible :location_name
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :locations
end
This all works fine during development, since I'm doing everything a step at a time; but if I try deploying this to my staging environment, I run into trouble.
The problem is that since my code has already changed to reflect the migration, it causes the environment to crash when it attempts to run the migration.
Has anyone else dealt with this problem? Am I resigned to splitting my deployment up into multiple steps?
UPDATE It appears I am mistaken; when migrating a coworker's environment we ran into trouble, but staging updated without issue. Mea Culpa. I'll mark #noodl's reply as the answer to bury this, his post is good advice anyway.
I think the solution here is to not write migrations which have any external dependencies. Your migrations should not depend on the past or current state of your model in order to execute.
That doesn't mean that you can't use your model objects, just that you shouldn't use the versions found in whatever version of your code happens to be installed when you run a particular migration.
Instead consider redefining your model objects within your migration file. In most cases I find that an empty model class extending ActiveRecord::Base or a very stripped down version of the model class I was using at the time I wrote the migration allows me to write a reliable, future proof, migration without needing to convert ruby logic into SQL.
#20110111193815_stop_writing_fragile_migrations.rb
class StopWritingFragileMigrations < ActiveRecord::Migration
class ModelInNeedOfMigrating < ActiveRecord::Base
def matches_business_rule?
#logic copied from model when I created the migration
end
end
def self.up
add_column :model_in_need_of_migrating, :fancy_flag, :boolean, :default => false
#do some transform which would be difficult for me to do in SQL
ModelInNeedOfMigrating.all.each do |model|
model.update_attributes! :fancy_flag => true if model.created_at.cwday == 1 && model.matches_business_rule?
#...
end
end
def self.down
#undo that transformation as necessary
#...
end
end
What error are you getting when you run the migrations? You should be fine as long as your rake files and migrations don't use your models (and they shouldn't).
You'll also want to switch the order of your drop_table and remove_column lines in the self.down of your migration.

Resources