I have the following Rails migration which works perfectly (irrelevant pieces removed):
create_table :comments do |t|
t.text :body
t.references :post
end
Now I'd like to add an author column to my comments table (which is the userid of a user), but I have no idea how to do it (I'm tempted to just write the MySql-specific syntax using an execute).
I've been looking at add_column here which doesn't mention references. I've actually found TableDefinition#references but I have no idea how to use it with an add_column statement.
Is this possible? Also, is it true that, for MySql, the "references" functionality does not actually establish relationships between the tables?
While it's too late to get any points out of this, I thought I'd post the best way for posterity :)
use change_table instead of create_table to add columns to a table that already exists, with all the TableDefinition goodness:
self.up do
change_table :comments do |t|
t.references :author
end
end
This might seem trivial, but other gems like Devise make heavy use of their own custom table definitions, and this way you can still use them.
add_reference :table_name, :reference, index: true
Finally got it
add_column :locations, :state_id , :integer, :references => "states"
First, do:
script/generate migration AddAuthorIdToComments
Open the generated file and add this line:
add_column :comments, :author_id, :integer
Then in your model files:
class User < ActiveRecord::Base
has_many :comments, :foreign_key => "author_id"
end
class Comment
belongs_to :author, :class_name => User
end
It's been a while since I've looked at this, but last I checked migrations don't support creating foreign keys. Fortunately, however, there is a plug-in for it. I've used this and it works well.
You could add the column by add_column(:table, :column_name, :type, :options) in a new 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'm looking at the foreigner gem and trying to create some foreign keys. However, the gems documentation says that you should create your foreign keys like this `add_foreign_key(from_table, to_table, options) but when I do that, it seems like it works backwards. For example, I have 3 models, Entry, Ingredient, and an association called EntryIngredient. Entry has many Ingredients through EntryIngredient, Ingredient has many Entries through EntryIngredients, and EntryIngredient belongs to both of these. Yet this is the code that works:
class EntryIngredient < ActiveRecord::Base
belongs_to :entry
belongs_to :ingredient
end
class CreateEntryIngredients < ActiveRecord::Migration
def self.up
create_table :entry_ingredients do |t|
t.references :entry
t.references :ingredient
t.integer :quantity
t.string :unit
t.timestamps
end
add_index :entry_ingredients, [:entry_id, :ingredient_id]
add_foreign_key :entry_ingredients, :entries, :dependent => :delete
add_foreign_key :entry_ingredients, :ingredients, :dependent => :delete
end
def self.down
drop_table :entry_ingredients
end
end
By the docs I would think that the foreign key should actually be added like this:
add_foreign_key :entries, :entry_ingredients, :dependent => :delete
add_foreign_key :ingredients, :entry_ingredients, :dependent => :delete
But when I run the migration, that returns me a
Mysql2::Error: Key column 'entry_ingredient_id' doesn't exist in table: ALTER TABLE `entries` ADD CONSTRAINT `entries_entry_ingredient_id_fk` FOREIGN KEY (`entry_ingredient_id`) REFERENCES `entry_ingredient`(id) ON DELETE CASCADE
Can someone explain to me whats happening here? Why am I dyslexic?
I've verified this, if someone thinks I'm still not getting it please explain to me. But, unless my English is bad, I believe that the gem is documented incorrectly. The correct syntax is actually:
add_foreign_key(to_table, from_table, options)
So with that I would do add_foreign_key :entry_ingredients, :entries, :dependent => :delete
This logic says to me, add a foreign key to the table entry_ingredients, from table entries as entry_id, with options...
While by the documentations logic I would use this code add_foreign_key :entries, :entry_ingredients, :dependent => :delete which says to me, add a foreign key from entries (as entry_id), to entry_ingredients, with options... However, what in fact happens is we are adding a foreign key to entries, from entry_ingredients (as entry_ingredients_id). This is wrong, and not the intended result. Here is the error I receive when following the documentations method to prove it:
Mysql2::Error: Key column 'entry_ingredient_id' doesn't exist in table: ALTER TABLE `entries` ADD CONSTRAINT `entries_entry_ingredient_id_fk` FOREIGN KEY (`entry_ingredient_id`) REFERENCES `entry_ingredients`(id) ON DELETE CASCADE
I've reported this as an issue on github so hopefully he fixes the documentation.
I created a many-to-many relationship in rails, here's my models and migrations
class Channel < ActiveRecord::Base
has_and_belongs_to_many :packages
validates_presence_of :name
end
class Package < ActiveRecord::Base
has_and_belongs_to_many :channels
validates_presence_of :name
end
class CreateChannelsPackages < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.references :channel
t.references :package
t.timestamps
end
add_index :channels_packages, :channel_id
add_index :channels_packages, :package_id
end
end
Then i have a multiple select, but when i try to save i get this error
SQLite3::ConstraintException: constraint failed: INSERT INTO "channels_packages" ("package_id", "channel_id") VALUES (1, 1)
I tried to remove the indexes from the migration but it didn't solve it, did somebody else have this problem?
Btw i'm using Rails 3.2.6 and sqlite3 1.3.6
I think gabrielhilal's answer is not quite correct: use of extra attributes in the join table is deprecated, thus you need to remove the timestamp in your migration, then it should work just fine with the has_and_belongs_to_many wich itself is not deprecated.
If you do need additional attributes in your join table, though, has_many :through is the way to go.
There is also another question with good answers on this topic:
Rails migration for has_and_belongs_to_many join table
I don't know if it is the reason of your problem, but the has_and_belongs_to_many association is deprecated.
According to the Rails Guide:
The use of extra attributes on the join table in a has_and_belongs_to_many association is deprecated. If you require this sort of complex behavior on the table that joins two models in a many-to-many relationship, you should use a has_many :through association instead of has_and_belongs_to_many.
I know that you are not adding any extra attribute to the join table, but try changing your migration to the below, which I think is the default:
class CreateChannelPackageJoinTable < ActiveRecord::Migration
def change
create_table :channels_packages, :id => false do |t|
t.integer :channel_id
t.integer :package_id
t.timestamps
end
end
end
I have two models Artist and Painting.
I added the following migration and executed rake db:migrate
class AddArtistReferenceToPaintings < ActiveRecord::Migration
self.up do
change_table :paintings do |t|
t.references :artist
end
end
end
This doesn't change anything in the database. What am I doing wrong?
Seems correct .
Did you already run this migration and added this latter ? If yes then create new one OR delete version from schema_migrations .
Way :
To add a foreign key column
change_table(:suppliers) do |t|
t.references :company
end
It creates a company_id(integer) column
To add a polymorphic foreign key column
change_table(:suppliers) do |t|
t.belongs_to :company, :polymorphic => true
end
It creates company_type(varchar) and company_id(integer) columns .
For further detail refer the link.
Try
t.belongs_to :artist
instead
t.references :artist
But your variant should work too, if you test in irb console. Run 'reload' to update.
I'd like to know the "proper" way to approach adding a relation between two existing classes in Rails 3.
Given existing models: Clown & Rabbit
I'd like to add a reference (belongs_to) from Rabbit to Clown. I start by trying to generate a migration:
rails g migration AddClownToRabbits clown:reference
which gives me a migration that looks like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown, :reference
end
def self.down
remove_column :rabbits, :clown
end
end
After rake db:migrate on this migration I examine SQLite3's development.db and see a new column: "clown" reference
I guess I was expecting a "clown_id" integer column and a migration that looked like:
class AddClownToRabbits < ActiveRecord::Migration
def self.up
add_column :rabbits, :clown_id
end
def self.down
remove_column :rabbits, :clown_id
end
end
I'm sure :reference is supposed to be equivalent to "t.references :clown" but I can't find the documentation (big surprise). API says add_column: Instantiates a new column for the table. The type parameter is normally one of the migrations native types, which is one of the following: :primary_key, :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time, :date, :binary, :boolean.
...with no reference to :reference.
If you are using edge rails (4.0) you can use:
rails generate migration AddAddressRefToContacts address:references
As you can see by the docs.
After you set belongs_to in Rabbit, and has_many in Clown, you can do a migration with:
add_column :rabbit, :clown_id, :integer
EDIT: See Paulo's answer below for a more updated answer (Rails 4+)
I'm not sure where you got this idea, but there is no (and never has been) such syntax to do what you want with add_column. To get the behavior you want, you'd have to do t.refences :clown, as you stated. In the background this will call: #base.add_column(#table_name, "#{col}_id", :integer, options).
See here.
EDIT:
I think I can see the source of your confusion. You saw the method call t.reference and assumed it was a datatype because calls such as t.integer and t.string exist, and those are datatypes. That's wrong. Reference isn't a datatype, it's just simply the name of a method, similar to t.rename is.