Rails Theory - No Database Dependencies? - ruby-on-rails

I was under the impression that with Rails you're not supposed to define any dependencies in the database, but rather just use your has_many and belongs_to stuff to define relationships. However, I'm going through the rails guide, and it has the following.
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :commenter
t.text :body
t.references :post
t.timestamps
end
add_index :comments, :post_id
end
end
I thought this wasn't okay...? I'm trying to do something like a comment field that creates a new instance each time you call the show method, but I think without these "references" and "add_index," it's not storing the post_id in the comment row.

All this migration does is create post_id and tells the database that it should index this column (improves performance)
t.references :post is basically the same as t.integer :post_id so, yes, it is storing the post_id in the comment. You'll still need to define your relationships in your models.

You are actually wrong on the philosophy.
Rails magic is good, only when backed at the DB level by actual foreign keys.
The docs clearly state this
Rails magic comes in, when you have correctly named your foreign keys, so that it can use the convention to figure out the associations.

What's wrong with expressing relationships within the ORM, that's where it's supposed to be done. I believe you are getting mixed up between db vendor specifics such as foreign key constraints and relationships.
class Comment < ActiveRecord::Base
attr_accessible :post, :post_id
belongs_to :post
end
class Post < ActiveRecord::Base
has_many :comments
end
class CommentsController < ApplicationController
def create
#comment = Comment.create(params[:comment]) # where params[:comment] = {post_id: 1, message: ''}
#post = comment.post
respond_with(#comment)
end
end

Related

Rails / Papertrail: Changeset with association changes

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.

Confusion about ActiveRecord associations and foreign keys

In the below example, do I have to create employee_id in the Office model, or is it created automatically by db:migrate?
class Employee < ActiveRecord::Base
has_one :office
end
class Office < ActiveRecord::Base
belongs_to :employee # foreign key - employee_id
end
Feels like I'm missing something fundamental. I'm trying to get a basic one to many relationship working, where I can use a drop-down select of objects from the one side. Are there any good basic tuts explaining how this works?
I had to create _ids in all the models where I wanted this to work, but it doesn't seem right from examples I've looked at.
two steps.
firstly, you have to create an employee_id field in the office table in the migration file. you will have something like that :
class CreateOffices < ActiveRecord::Migration
def change
create_table :offices do |t|
t.string :name
t.integer :employee_id
t.timestamps
end
end
end
secondly, you have to define the association in the model. by convention, if you name the foreign_key field employee_id, you don't have to specify the name of it in the model.
class Office < ActiveRecord::Base
belongs_to :employee
end
should be enough.
Associations in ActiveRecord comprise two parts. Hooking together the model objects (like you've done) and setting up the database. So you'll need to define the association in your migration like so:
def change
create_table :offices do |t|
# Other migrations
t.references :employee
end
end
Alternatively you can do t.integer :employee_id which will achieve the same end too.

Rails Many to Many SQLite3 error

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

How to Nest Models within a Model

Imagine I have two models
Film
-name
-description
-duration
-year_made
-rating
-actors
Actor
-name
-d_o_b
-biography
-films
Actors are nested in a Film and vice versa.
How do I represent this relationship in my Ruby models? Realistically I would have a third table mapping actor_id with film_id.
Whilst adding details to a film I would like to be able to create an actor on the fly(if an actor does not exist create a new one with the name supplied)
Thank you in advance.
ADDITION:
Just found a link to a similar question.
You're looking at a Has and Belongs to Many (HABTM) relationship between the two tables.
Read about HABTM relationship in the Rails guides here: http://edgeguides.rubyonrails.org/association_basics.html#has_and_belongs_to_many-association-reference
First you'll need to generate a migration which will look something like this:
class AddActorFilmTable < ActiveRecord::Migration
def self.up
create_table :actors_films, :id => false do |t|
t.integer :actor_id, :null => :false
t.integer :film_id, :null => :false
end
add_index :actors_films, [:actor_id, :film_id], :unique => true
end
def self.down
drop_table :actors_films
end
end
and then specify in your models:
class Actor < ActiveRecord::Base
has_and_belongs_to_many :films
end
class Film < ActiveRecord::Base
has_and_belongs_to_many :actors
end
This will allow you to use all of the additional Rails methods for this type of relationship. To use this in a form, you could follow RailsCast 17: HABTM Checkboxes - though it's old, it should still apply. Alternatively, you can use a gem like Simple Form to easily generate the associations for you like so:
form_for #actor do |f|
f.collection_check_boxes :film_ids, Film.all, :id, :name
end

Scaffolding ActiveRecord: two columns of the same data type

Another basic Rails question:
I have a database table that needs to contain references to exactly two different records of a specific data type.
Hypothetical example: I'm making a video game database. I have a table for "Companies." I want to have exactly one developer and exactly one publisher for each "Videogame" entry.
I know that if I want to have one company, I can just do something like:
script/generate Videogame company:references
But I need to have both companies. I'd rather not use a join table, as there can only be exactly two of the given data type, and I need them to be distinct.
It seems like the answer should be pretty obvious, but I can't find it anywhere on the Internet.
Just to tidy things up a bit, in your migration you can now also do:
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
And since you're calling the keys developer_id and publisher_id, the model should probably be:
belongs_to :developer, :class_name => "Company"
belongs_to :publisher, :class_name => "Company"
It's not a major problem, but I find that as the number of associations with extra arguments get added, the less clear things become, so it's best to stick to the defaults whenever possible.
I have no idea how to do this with script/generate.
The underlying idea is easier to show without using script/generate anyway. You want two fields in your videogames table/model that hold the foreign keys to the companies table/model.
I'll show you what I think the code would look like, but I haven't tested it, so I could be wrong.
Your migration file has:
create_table :videogames do |t|
# all your other fields
t.int :developer_id
t.int :publisher_id
end
Then in your model:
belongs_to :developer, class_name: "Company", foreign_key: "developer_id"
belongs_to :publisher, class_name: "Company", foreign_key: "publisher_id"
You also mention wanting the two companies to be distinct, which you could handle in a validation in the model that checks that developer_id != publisher_id.
If there are any methods or validation you want specific to a certain company type, you could sub class the company model. This employs a technique called single table inheritance. For more information check out this article: http://wiki.rubyonrails.org/rails/pages/singletableinheritance
You would then have:
#db/migrate/###_create_companies
class CreateCompanies < ActiveRecord::Migration
def self.up
create_table :companies do |t|
t.string :type # required so rails know what type of company a record is
t.timestamps
end
end
def self.down
drop_table :companies
end
end
#db/migrate/###_create_videogames
class CreateVideogames < ActiveRecord::Migration
create_table :videogames do |t|
t.belongs_to :developer
t.belongs_to :publisher
end
def self.down
drop_table :videogames
end
end
#app/models/company.rb
class Company < ActiveRecord::Base
has_many :videogames
common validations and methods
end
#app/models/developer.rb
class Developer < Company
developer specific code
end
#app/models/publisher.rb
class Publisher < Company
publisher specific code
end
#app/models/videogame.rb
class Videogame < ActiveRecord::Base
belongs_to :developer, :publisher
end
As a result, you would have Company, Developer and Publisher models to use.
Company.find(:all)
Developer.find(:all)
Publisher.find(:all)

Resources