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.
Related
I am stuck. I've been trying to figure out how to include the association changes (has_many, has_many through) on a model that has papertrail. I would like to call MyModel.versions.first.changeset and have any changes that took place on associated objects be included in the .changeset hash that is returned from that version of the object.
I've added the migrations for version associations:
class CreateVersionAssociations < ActiveRecord::Migration
def self.up
create_table :version_associations do |t|
t.integer :version_id
t.string :foreign_key_name, :null => false
t.integer :foreign_key_id
end
add_index :version_associations, [:version_id]
add_index :version_associations, [:foreign_key_name, :foreign_key_id], :name => 'index_version_associations_on_foreign_key'
end
def self.down
remove_index :version_associations, [:version_id]
remove_index :version_associations, :name => 'index_version_associations_on_foreign_key'
drop_table :version_associations
end
end
class AddTransactionIdColumnToVersions < ActiveRecord::Migration
def self.up
add_column :versions, :transaction_id, :integer
add_index :versions, [:transaction_id]
end
def self.down
remove_index :versions, [:transaction_id]
remove_column :versions, :transaction_id
end
end
I have added Papertrail to the associated objects, but as far as I can tell, there is no documentation discussing retrieving changes that took place on the associated objects. Can anyone assist on if this is possible using Papertrail?
I am trying to implement an audit trail of changes on a model and its associated objects that can be accessed in one changeset.
The information you need is ultimately stored in the relevant tables versions and version_associations.
However, paper_trail does not provide the methods for you to access the information in the way you want. But you can write a custom method yourself to get a list of the associations's versions of an object.
Let's say you have the following models:
class Article < ApplicationRecord
has_many :comments
has_paper_trail
end
class Comment < ApplicationRecord
belongs_to :article
has_paper_trail
end
You can find all the comment versions of an article object article this way:
PaperTrail::Version.where(item_type: 'Comment')
.joins(:version_associations)
.where(version_associations: { foreign_key_name: 'article_id', foreign_key_id: article.id })
.order('versions.created_at desc')
You can monkey patch the gem, or define this method as an instance method on the Article class so you can call it easily, eg article.comment_versions
Note, the above information isn't available in the article.versions.first.changeset, because if you change a comment but not the article, the article is not versioned, only the comment is.
But the method above allows you to access the history of changes to the associations.
Looks like this has been added as an experimental feature to the papertrail gem
check out the docs here
https://github.com/airblade/paper_trail/blob/v4.2.0/README.md#associations
This change will require the addition of a new database table for papertrail to keep track of associated models.
I am pretty new to rails so this may be a dumb question, but I have spent hours debugging this seemingly simple error and gotten nowhere. In my database, I want each row to to store an array of numbers (in a variable called contributions). I read that the best way to do that was by adding the following code:
class Goal < ActiveRecord::Base
#...
serialize :contributions, Array
#...
end
The attribute is also in goal.rb
ActiveRecord::Schema.define(version: 20150711171452) do
create_table "goals", force: :cascade do |t|
#...
t.decimal "contributions"
#...
end
end
I added it via this migration:
class AddContributorsToGoals < ActiveRecord::Migration
def change
add_column :goals, :contributors, :string
add_column :goals, :contributions, :decimal
end
end
The only problem is that whenever I try to access this attribute, I always get errors stating that it is of type BigDecimal. Rails thinks that this is a single number, but I want it to be a list of numbers. Any ideas as to what I could be doing wrong?
One example of a problem I get is that when this executes:
#goal.contributions << #goal.lastUpdateAmount
but I get this error
undefined method `<<' for #<BigDecimal:7f95e082ab18,'-0.0',9(9)>
I followed identical steps with an array of strings and that was serialized properly. I just don't know what's going on with this one.
Please change data type decimal to text
class AddContributorsToGoals < ActiveRecord::Migration
def change
add_column :goals, :contributions, :text
end
end
I am using ruby 1.8.7 and rails 1.2.6. (Its old I know. But I have to use it.) I need to add column to users table. I cant use rails generate migration with rails 1.2.6. I need to add a versioned db migrate file. How can I do that?
I want to add product column to users table. I created a file in the db/migrate folder with following contents.
class AddProductToUser < ActiveRecord::Migration
def self.up
add_column :users, :product, :string
end
def self.down
remove_column :users, :product
end
end
I used script/generate migration AddProductToUser. It gives an error as
undefined method 'cache' for Gem:Module.
Any pointers on how to run migration in rails 1.2.6(<2.x) will also be useful.
Your migrate file looks (almost) fine, does the filename match the class name, and does it have a sequence number at the beginning that follows on from the previous migrate?
Slight change:
# db/migrate/123_add_product_to_user.rb
class AddProductToUser < ActiveRecord::Migration
def self.up
add_column :users, :product, :string
end
def self.down
remove_column :users, :product # note sure where radius came from?
end
end
Then should just be run with rake migrate
More info at apidock
I would like to add a has_many relationship to two existing tables/models in my app & I'm not too sure how to di it?
When I did this before with a new model the rails generate command handled everything for me, with just rails generate model Photo image:string hikingtrail:references it created the below migration
class CreatePhotos < ActiveRecord::Migration
def change
create_table :photos do |t|
t.string :image
t.references :hikingtrail
t.timestamps
end
add_index :photos, :hikingtrail_id
end
end
Now I would like set up a relationship between users & photos with each user has_many :photos.
When I generate a migration to achieve this it does not include the add_index :photos, :user_id, is this something I should be doing manually or are the below steps enough for setting up this relationship in my database?
rails g migration AddUserIdToPhotos user_id:integer
which creates...
class AddUserIdToPhotos < ActiveRecord::Migration
def change
add_column :photos, :user_id, :integer
end
end
& then run...
rake db:migrate
It is enough to set up your relationship. You can add a index to improve the speed of your record searching. In fact some recommend to put a index to all the foreign keys. But don't worry about this now, i guess you are not going to have that many records to use a index.
If you have already migrated everything and want to add a index make do:
rails g migration AddIndexToUserIdToPhotos
and inside add the index column:
class AddUserIdToPhotos < ActiveRecord::Migration
def change
add_index :photos, :user_id
end
end
I'm reading Rails 3 in Action and following the commands verbatim. However, when I run the commands
rails new things_i_bought
cd things_i_bought
bundle install
rails generate scaffold purchase name:string cost:float
The book says I should get this code:
class CreatePurchases < ActiveRecord::Migration
def self.up #not created in my code
create_table :purchases do |t|
t.string :name
t.float :cost
t.timestamps
end
end
def self.down # not created in my code
drop_table :purchases
end
end
I get this code instead:
class CreatePurchases < ActiveRecord::Migration
def change
create_table :purchases do |t|
t.string :name
t.float :cost
t.timestamps
end
end
end
Why are the class methods up and down not being created for me? I'm using
rails 3.1.1 and ruby 1.9.2.
thanks for reading my book!
As JacobM and dbalatero have already explained, this is a new feature in Rails 3.1. This particular feature was added by Aaron Patterson as a way to simplify the migration syntax. In earlier versions of Rails, you would have to do as the book shows:
class CreatePurchases < ActiveRecord::Migration
def self.up
create_table :purchases do |t|
t.string :name
t.float :cost
t.timestamps
end
end
def self.down
drop_table :purchases
end
end
But that's kind of repeating yourself. Aaron created a migration syntax that looks good and is simpler, calling only the methods necessary for migrating forward, but also allowing the migrations backwards (known as a "rollback") too. The same migration written with the Rails 3.1 syntax is this:
class CreatePurchases < ActiveRecord::Migration
def change
create_table :purchases do |t|
t.string :name
t.float :cost
t.timestamps
end
end
end
So when this migration runs "forwards", Rails will create the purchases table with the fields. When you roll it back (or run it "backwards") then Rails will know to drop the table.
This syntax isn't entirely perfect however, and you'll run into problems with methods such as change_column. When that happens, it's best to stick with defining both the def up and def down methods in the migrations:
class CreatePurchases < ActiveRecord::Migration
def up
change_column :purchases, :cost, :integer
end
def down
change_column :purchases, :cost, :float
end
end
That's because in this example Rails won't know how to switch it back to the previous type. I hope this explains it better!
This is a new feature in Rails 3.1. For changes that Rails can figure out how to reverse, such as creating a table, you simply create a "change" method with the code that would have gone in "up", and it figures out how to do "down" on it's own.
You can also define "up" and "down" methods yourself -- for some changes (such as dropping a column) Rails won't be able to figure it out -- but the syntax is a bit different; it's not just def up instead of def self.up (they're now instance methods instead of class methods).
I believe in the new Rails 3.1, the database migration methods are self-aware about how to run an up/down migration.
Therefore, if you define a def change method, it will try to use those self-aware methods: in this case, create_table knows to do DROP TABLE in a down context, and CREATE TABLE in an up context.
If you want the old style, you can probably keep using it and define your own self.down and self.up methods as the book describes.
Edit: here's a link to the blog post on this, called "Reversible Migrations": http://www.edgerails.info/articles/what-s-new-in-edge-rails/2011/05/06/reversible-migrations/index.html