I am making a Viewer model with
belongs_to :users
belongs_to :orders
that joins the models Users and Orders with a :has_many :through => :viewers.
And the Viewer model has the attributes of user_id and order_id.
How would I set it up so that new viewers are only accepted if both user_id and order_id are unique in the same row? I remember in MySQL being able to do so with a flag (although I can't for the life of me remember what it was), but I'm not sure how to do it with Rails.
Can I do something like (for Viewer.rb) validates_uniqueness_of :user_id, :scope => :order_id?
Oh.
I think the way to do so is as follows:
in the Viewer model migration file (ie: I should have done this earlier)
def self.up
#create_table code
end
add_index :viewers, [:user_id, :order_id], :unique => true
end
Related
I would like to create a structure of Users having many friends, also of class User:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends, class_name: "User"
end
I do not need any details of their relationship thus I do not use :through with kind of class Friendship. But now I cannot find any way how to create corresponding database (neither with migration file nor using rails g model User username:string ... command). Any ideas?
Here are some resources which may be helpful:
RailsCasts episode #163 Self-Referential Association regarding self-referential many-to-many relationships
RailsCasts episode #47 Two Many-to-Many. This might be more relevant to what you're attempting to accomplish
A gist someone created for self-referential relationships using HABTM
I'll summarize the information found in those links:
Given that you're describing a self-referential many-to-many relationship, you will of course end up with a join table. Normally, the join table should be deliberately named in such a way that Rails will automatically figure out which models the table is joining, however the "self-referential" part makes this a tad awkward, but not difficult. You'll merely have to specify the name of the join table, as well as the joining columns.
You'll need to create this table using a migration that will probably look something like this:
class CreateFriendships < ActiveRecord::Migration
def self.up
create_table :friendships, id: false do |t|
t.integer :user_id
t.integer :friend_user_id
end
add_index(:friendships, [:user_id, :friend_user_id], :unique => true)
add_index(:friendships, [:friend_user_id, :user_id], :unique => true)
end
def self.down
remove_index(:friendships, [:friend_user_id, :user_id])
remove_index(:friendships, [:user_id, :friend_user_id])
drop_table :friendships
end
end
I'm not certain whether there is a shortcut way of creating this table, but bare minimum you can simply do rails g migration create_friendships, and fill in the self.up and self.down methods.
And then finally in your user model, you simply add the name of the join table, like so:
class User < ActiveRecord::Base
has_and_belongs_to_many :friends,
class_name: "User",
join_table: :friendships,
foreign_key: :user_id,
association_foreign_key: :friend_user_id
end
As you can see, while you do have a join table in the database, there is no related join model.
Please let me know whether this works for you.
I have created models in rails using
rails g model User uid:integer name:string
and
rails g model Post message:string user:references
The above lines generated models like following
class User < ActiveRecord::Base
attr_accessible :name, :uid
end
and
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :message
end
Now do i need to explicitly add a has_many :posts in the User model ?
also how can i make user_id in Post model NOT NULL ?
Thanks.
Now do i need to explicitly add a has_many :posts in the User model ?
Yes if you want to find posts of user User.firs.posts
also how can i make user_id in Post model NOT NULL ?
Add migration
change_column :posts, :user, :integer, :null => false
You don't necessarily need the has_many :posts unless you're going to make queries like current_user.posts. Rails uses associations to tie in queries like that.
In order to make sure user_id is not null you can do one or both.
Add validation in your model like so:
validates :user_id, :presence => true
Add a database contraint to make it so that the database also does not allow null values for that
t.integer "user_id", :null => false
The latter will make the database consistent by not allowing you to insert values null through SQL queries.
I have two models Group and Person that I want to have a many-to-many relationship, but I'm unclear on how to manage the relationship itself. I want to be able to create groups and persons separately -- NOT necessarily via a nested model -- and then link persons to groups from the group view/model itself.
Does anyone have any suggestions on how to do so?
I thought of creating a many-to-many relationship via a join model and then accepting nested attributes for the join model in the Group model -- so I believe I will be able to add and remove relationships via the Group view/model. Does this approach make sense?
I would create a PersonGroup model that looks like this:
class PersonGroup < ActiveRecord::Base
has_many :people
has_many :groups
end
And you might also do rails generate migration create_person_group and put this in the up method of the generated migration file:
create_table :person_group do |t|
t.integer :person_id, :null => false
t.integer :group_id, :null => false
t.timestamps
end
add_index :person_group, [:person_id, :group_id], :unique => true
Then in Person:
class Person < ActiveRecord::Base
has_many :person_groups
has_many :groups, :through => :person_groups
end
And in Group:
class Group < ActiveRecord::Base
has_many :person_groups
has_many :people, :through => :person_groups
end
Create a junction table. Junction tables are used when you want to store many-to-many relationships. I don't develop in ROR so I don't know the specifics for ActiveRecord but I am sure that this can help you think about the problem as well.
group_id INTEGER,
person_id INTEGER
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
Suppose the following DB migration in Ruby:
create_table :question_votes do |t|
t.integer :user_id
t.integer :question_id
t.integer :vote
t.timestamps
end
Suppose further that I wish the rows in the DB contain unique (user_id, question_id) pairs. What is the right dust to put in the model to accomplish that?
validates_uniqueness_of :user_id, :question_id seems to simply make rows unique by user id, and unique by question id, instead of unique by the pair.
validates_uniqueness_of :user_id, :scope => [:question_id]
if you needed to include another column (or more), you can add that to the scope as well. Example:
validates_uniqueness_of :user_id, :scope => [:question_id, :some_third_column]
If using mysql, you can do it in the database using a unique index. It's something like:
add_index :question_votes, [:question_id, :user_id], :unique => true
This is going to raise an exception when you try to save a doubled-up combination of question_id/user_id, so you'll have to experiment and figure out which exception to catch and handle.
The best way is to use both, since rails isn't 100% reliable when uniqueness validation come thru.
You can use:
validates :user_id, uniqueness: { scope: :question_id }
and to be 100% on the safe side, add this validation on your db (MySQL ex)
add_index :question_votes, [:user_id, :question_id], unique: true
and then you can handle in your controller using:
rescue ActiveRecord::RecordNotUnique
So now you are 100% secure that you won't have a duplicated value :)
From RailsGuides. validates works too:
class QuestionVote < ActiveRecord::Base
validates :user_id, :uniqueness => { :scope => :question_id }
end
Except for writing your own validate method, the best you could do with validates_uniqueness_of is this:
validates_uniqueness_of :user_id, :scope => "question_id"
This will check that the user_id is unique within all rows with the same question_id as the record you are attempting to insert.
But that's not what you want.
I believe you're looking for the combination of :user_id and :question_id to be unique across the database.
In that case you need to do two things:
Write your own validate method.
Create a constraint in the database
because there's still a chance that
your app will process two records at
the same time.
When you are creating a new record, that doesn't work because the id of your parent model doesn't exist still at moment of validations.
This should to work for you.
class B < ActiveRecord::Base
has_many :ab
has_many :a, :through => :ab
end
class AB < ActiveRecord::Base
belongs_to :b
belongs_to :a
end
class A < ActiveRecord::Base
has_many :ab
has_many :b, :through => :ab
after_validation :validate_uniqueness_b
private
def validate_uniqueness_b
b_ids = ab.map(&:b_id)
unless b_ids.uniq.length.eql? b_ids.length
errors.add(:db, message: "no repeat b's")
end
end
end
In the above code I get all b_id of collection of parameters, then compare if the length between the unique values and obtained b_id are equals.
If are equals means that there are not repeat b_id.
Note: don't forget to add unique in your database's columns.