Mixing has_one and has_and_belongs_to_many associations - ruby-on-rails-3

I'm trying to build a database of urls(links). I have a Category model that has and belongs to many Links.
Here's the migration I ran:
class CreateLinksCategories < ActiveRecord::Migration
def self.up
create_table :links_categories, :id => false do |t|
t.references :link
t.references :category
end
end
def self.down
drop_table :links_categories
end
end
Here's the Link model:
class Link < ActiveRecord::Base
validates :path, :presence => true, :format => { :with => /^(#{URI::regexp(%w(http
https))})$|^$/ }
validates :name, :presence => true
has_one :category
end
Here's the category model:
class Category < ActiveRecord::Base
has_and_belongs_to_many :links
end
And here's the error the console kicked back when I tried to associate the first link with the first category:
>>link = Link.first
=> #<Link id: 1, path: "http://www.yahoo.com", created_at: "2011-01-10...
>>category = Category.first
=> #<category id : 1, name: "News Site", created_at: "2011-01-11...
>>link.category << category
=> ActiveRecord::StatementInvalid: SQLite3::Exception: no such column :
categories.link_id:
Are my associations wrong or am I missing something in the database? I expected it to find the links_categories table. Any help is appreciated.

Why are you using HABTM here at all? Just put a belongs_to :category on Link, and a has_many :links on Category. Then in the db, you don't need a join table at all, just a :category_id on the links table.
But, if you do want a HABTM here, from a quick glance, the first thing I noticed is that your join table is named incorrectly -- it should be alphabetical, categories_links.
The second thing is that you can't mix has_one and has_and_belongs_to_many. HABTM means just that -- A has many of B and A belongs to many of B; this relationship implies that the opposite must also be true -- B has many of A and B belongs to many of A. If links HABTM cateogries, then categories must HABTM links.
See http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many

Related

ActiveRecord many to many should I make another table to hold the relation information?

I have albums and artists tables that should relate as many to many: each album can be owned by multiple artists and each artist can have multiple album. But I need to know whether the artist is the main artist or just a contributor.
Currently I just use artists column in albums table to hold artists id in semicolon separated strings (format: 3;6;343;56;1). The main artist's id should appear first, the rest is just contributors.
Currently I access artist's contribution in an albums by query with .where and LIKE keywords. Then filter the array result to exclude prefix (artist id), based on this answer.
#albums_ = Album.where("artists LIKE :prefix", prefix: "%#{params[:id]}%")
#albums_contribute_to = #albums_.select { |album| !album.artists.start_with?("#{params[:id]}") }
Is there any more effective way to accomplish this?
Played around with it for a little bit, the big idea was to put some additional information on the join table (in my example, I call it primary). And then read the docs to figure out how to tell ActiveRecord to use them.
# setup active record
require 'active_record'
ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: ':memory:'
# some tables (migrations / schema)
ActiveRecord::Schema.define do
create_table(:artists) { |t| t.string :name}
create_table(:albums) { |t| t.string :name }
create_table :appearances do |t|
t.integer :artist_id
t.integer :album_id
t.boolean :primary, default: false # <-- join table specifies who is primary
end
end
# some models
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
end
class Album < ActiveRecord::Base
# three associations to appearances
has_many :all_appearances, class_name: 'Appearance'
has_many :primary_appearances, -> { where primary: true }, class_name: 'Appearance'
has_many :featured_appearances, -> { where primary: false }, class_name: 'Appearance'
# three associations to artists
has_many :all_artists, through: 'all_appearances', source: 'artist'
has_many :primary_artists, through: 'primary_appearances', source: 'artist'
has_many :featured_artists, through: 'featured_appearances', source: 'artist'
end
# some artists
dre = Artist.create! name: 'Dr. Dre'
dogg = Artist.create! name: 'Snoop Dogg'
slim = Artist.create! name: 'Eminem'
# some albums
weed = Album.create! name: 'The Chronic 2001',
primary_artists: [dre],
featured_artists: [dogg, slim]
show = Album.create! name: 'The Eminem Show',
primary_artists: [slim],
featured_artists: [dre]
# it understands the associations
weed.all_artists.pluck :name # => ["Dr. Dre", "Snoop Dogg", "Eminem"]
weed.primary_artists.pluck :name # => ["Dr. Dre"]
weed.featured_artists.pluck :name # => ["Snoop Dogg", "Eminem"]
# might be nice to add similar scoped associations to get from Artist to Album
weed # => #<Album id: 1, name: "The Chronic 2001">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 1, name: "Dr. Dre">]>
.first # => #<Artist id: 1, name: "Dr. Dre">
.albums # => #<ActiveRecord::Associations::CollectionProxy [#<Album id: 1, name: "The Chronic 2001">, #<Album id: 2, name: "The Eminem Show">]>
.last # => #<Album id: 2, name: "The Eminem Show">
.primary_artists # => #<ActiveRecord::Associations::CollectionProxy [#<Artist id: 3, name: "Eminem">]>
.first # => #<Artist id: 3, name: "Eminem">
It is typical case for using joined table. If you want to create that kind of relationship. What you are doing will only create problems in the long run as you will have to hold that information in memory, which is also slow.
In ruby on rails that relation would like something along this lines:
class Album < ApplicationRecord
has_many :album_artists
has_many :artists, through: :album_artists
end
class AlbumArtist < ApplicationRecord
belongs_to :artist
belongs_to :album
end
class Artist < ApplicationRecord
has_many :album_artists
has_many :albums, through: :album_artists
end
If you would like to avoid doing that with ruby on rails you have to implement custom query but the rules are the same, you have to create some kind of joined table.
Agree with Joshua and Przemek, it's an easy solution. here is how to use it:
class Appearance < ActiveRecord::Base
belongs_to :artist
belongs_to :album
# has attribute "primary"
end
class Artist < ActiveRecord::Base
has_many :appearances
has_many :albums, through: :appearances
end
class Album < ActiveRecord::Base
has_many :appearances
has_many :artists, through: :appearances
end
# create album with 2 artists
Album.create(
name: "foo_bar",
appearances: [
Appearance.new(artist: Artist.create(name: "foo"), primary: true),
Appearance.new(artist: Artist.create(name: "bar"))
]
)
# list artists
album = Album.first
album.appearances.order("piramary desc").each do |appearance|
puts "#{appearance.artist.name} #{appearance.primary? ? "(Primary)" : ""}"
end
I really don't think you need a separate model that joins the Album and Artist one. So you can use the has_and_belong_to_many(HABTM) method that is coming from ActiveRecord and is used for transitive many to many relations. You will also need a table that joins the 2 models and you can easily create one with a migration.
I highly recommend you to read this article. It will be really helpful for you.

Ruby on Rails: create an instance that "belongs_to" another Class

Well, i'm new on rails, and I has the following:
class Post < ActiveRecord::Base
belongs_to :category
...
end
and
class Category < ActiveRecord::Base
has_many :posts
...
end
I want to create a post, and select its category from a drop down with:
...
select("post", "category", Post::CATEGORIES, {:include_blank => true})
...
When I try to save, it says that category attribute doesnt exists.
How can I do it?
The post table contains only category id, so you need to assign to category_id:
select("post", "category_id", Post::CATEGORIES, :include_blank => true)

Many to many relationship with metadata stored in the mapping table in activerecord

I have two tables with a many to many relationship, through a third table. In the third table is a piece of data I need to assign when I build the relationships between the two tables, how can I use ActiveRecords build method to assign that?
Here is code to show what I mean:
class Company < Contact
has_many :contact_companies
has_many :people, :through => :contact_companies
accepts_nested_attributes_for :people, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class Person < Contact
has_many :contact_companies
has_many :companies, :through => :contact_companies
accepts_nested_attributes_for :companies, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class ContactCompany < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
ContactCompany contains a data member called "position". What I want to do is something like:
c = Person.new
c.companies.build(:name => Faker::Company.name, :position => positions.sample)
EDIT:
When I try the code above I get "unknown attribute: position".
The c.companies.build line is attempting to build a Company object which does not have the position attribute (the ContactCompany does) hence the error. It looks like you are trying to set attributes on two different models, so you'll have to make sure you are setting the appropriate attribute on the right model:
# you can chain these calls but I separated them for readability
cc = c.contact_companies.build(:position => positions.sample)
cc.build_company(:name => Faker::Company.name)

Rails 3 uniqueness validation with scope on polymorphic table

I've got the following setup:
class Vote < ActiveRecord::Base
belongs_to :voteable, :polymorphic => :true, :counter_cache => true
end
class Proposition < ActiveRecord::Base
has_many :votes, :as => :voteable
end
class Winner < ActiveRecord::Base
has_many :votes, :as => :voteable
end
The Vote table looks like this:
t.string "ip_address"
t.integer "voteable_id"
t.string "voteable_type"
I want to validate the following. A user with a given ip_address can only vote on 1 proposition. So the combination of ip_address, voteable_id and voteable_type needs to be unique.
How can i achieve this with a "simple" validation rule?
To guarantee uniqueness you have to add unique index to your DB
If you don't have important data yet you can do it inside migration with add_index
add_index(:votes, [:ip_address, :voteable_id, voteable_type], :unique => true, :name => 'allowed_one_vote')
in case you already have some data it can be done with SQL and it depends on your DBMS
Add a scope to a unique :ip_address validation
class Vote < ActiveRecord::Base
# ...
validates :ip_address, uniqueness: { scope: [:voteable_type, :voteable_id]}
end

How do i create an object if it has more than one belongs_to?

I have the following:
class Org < ActiveRecord::Base
has_many :users
has_many :entries
end
class Entry < ActiveRecord::Base
belongs_to :org
belongs_to :user
validates_presence_of :entry_text
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end
I can Create Orgs and Users... How do i create an entry if there are two belongs_to? and what is this pattern called?
Double nested resources are tricky. The trick with users usually is to keep it out of your desired entry path.
Your question is kind of broad, but if you specify more information, people would be able to help you better. Also, I would recommend using the gem Devise for your user management system. Since you're using 'users' I would assume you want users from orgs to create entries. The entry created would be a part of org and the user would be the session's current user. Sorry if I am wrong to assume this.
Your routes.rb file can look something like this (assuming rails 3):
resources :orgs do
resources :entries
end
Then the create of your entry controller would look like:
#entry = #org.entries.new(params[:topic])
#entry.user = current_user #or however you are managing the current user's session.
And you'd want to set the org for the entire class by making a method that loads your current org and do a before_filter :loadOrg
def loadOrg
#org = Org.find(params[:id])
end
This is of course assuming your path is something like: /org/(id)/entry/(entry_id)
and not
/org/(id)/user/(user_id)/entry/(entry_id)
which in my opinion is unnecessary and can lead to more problems. You can always create a userpage model that calls all entries by users, but the default route doesn't necessarily have to include users in the path.
I don't see any problem.
#entry = Entry.create(:entry_text => "Hello World!")
Now questions to clarify what do you need:
Can #entry belongs both org and user at the same time? Or it can belongs to only one of them?
Should #entry belongs to at least one of them?
If #entry supposed to belong only one of them, so you should use Polymorphism
http://railscasts.com/episodes/154-polymorphic-association
class Entry < ActiveRecord::Base
belongs_to :textable, :polymorphic => true
validates_presence_of :entry_text
end
class Org < ActiveRecord::Base
has_many :users
has_many :entries, :as => :textable
end
class User < ActiveRecord::Base
belongs_to :org
has_many :entries, :as => :textable
validates_uniqueness_of :user_name
validates_presence_of :user_name, :length => { :minimum => 3 }
end