Ruby on Rails ActiveRecord "has_many :through" uniqueness validation - ruby-on-rails

Currently I insert a new relationship by everytime checking, if it doesn't exist:
unless Relationship.exists?(:entry_id => entry.id, :tag_id => tag.id)
How could I implement such validation inside the Relationship model, so that it wouldn't allow to have more than one relationship between the same entry and tag?

class Relationship < ActiveRecord::Base
belongs_to :entry
belongs_to :tag
validates :tag_id, :uniqueness => { :scope => :entry_id }
end

Assuming your models look something like this:
class Entry < ActiveRecord::Base
has_many :relationships
has_many :tags, :through => :relationships
end
class Tag < ActiveRecord::Base
has_many :relationships
has_many :entries, :through => :relationships
end
class Relationship < ActiveRecord::Base
belongs_to :entry
belongs_to :tag
end
You could add a unique validation to your Relationship join model:
validates_uniqueness_of :tag_id, :scope => :entry_id
The validates_uniqueness_of method will ensure that the Relationship doesn't already exist, and the :scope option will scope the match to the given column. The SQL generated by rails via this validation will look like:
SELECT `relationships`.id
FROM `relationships`
WHERE (`relationships`.`tag_id` = <tag id> AND `relationships`.`entry_id` = <entry id>)
LIMIT 1
(which you'll notice is essentially the same SQL generated by your explicit use of Relationship.exists?(:entry_id => entry.id, :tag_id => tag.id)), and if a record is found, validation will fail.
As well, as with any case where you want to validate uniqueness, ensure that you have a unique key on tag_id, entry_id in your relationships table. See this article and the "Concurrency and integrity" of the API page I linked above for more info.

Related

How do I validate uniqueness of join table with extra column in Ruby on Rails

I have a has many through relationship in a model that is self referencing. In the join table I also have an extra column that defines the source of the relationship. When adding a new object to that relationship I'd like to avoid duplicates in the join table based on the user_id, friend_id, and source_id
User Model
class User < ActiveRecord::Base
has_many :friendships
has_many :friends, :class_name => "User", :through => :friendships
end
Join Model
class Friendship < ActiveRecord::Base
attr_accessible :friend_id, :user_id, :source_id, :alert, :hide
# Relationships
belongs_to :user
belongs_to :friend, :class_name => "User"
has_one :source
end
I understand that I can do this
unless user.friends.include?(newFriend)
user.friendships.build(:friend_id => friendUser.id, :source_id => source.id)
end
But that seems like it will check to see if the new user exists in the current user's friends. I need to check on the join model level and make sure that the connection doesn't exist with the given source id.
I know there are several ways to accomplish this, but I'm pretty new to ruby on rails and am looking for the "rails way" to do it.
You can validate based on multiple columns in your intermediate table like this:
validates_uniqueness_of :user_id, :scope => [:friend_id, :source_id]

Rail Model: Using validates_uniqueness_of with scope on attributes of associated entity?

I have Model defined as below
class One <Active:Record:Base
{
has_and_belongs_to_many :twos, {:join_table => 'map__ones__twos'}
}
class Two <Active:Record:Base
{
has_and_belongs_to_many :ones, {:join_table => 'map__ones__twos'}
}
I want that name attribute of two should be unique for scope of one. That means all of twos belonging to one should have unique name. Here i can not specify some thing like below in Two model
validates_uniqueness_of :name, :scope => one_id
because on_id is not a column of twos table. Rather one_id and two_id are mapped to each other through table map_ones_twos (many to many relationship)
Please suggest
I have often found that using has_and_belongs_to_many is more trouble than it's worth. When I have many-to-many relationships, I create a join table and make a model for it. Then, the join model can have a validation on the uniqueness of the two ids.
Excuse the names. I am using the example table names from your question. In your case, this would look like:
class MapOnesTwo < ActiveRecord::Base
belongs_to :one
belongs_to :two
validates_presence_of :one_id, :two_id
validates_uniqueness_of :one_id, :scope => :two_id
end
Your One model looks like this:
class One < ActiveRecord::Base
has_many :ones_twos, :dependent => :destroy
has_many :twos, :through => :ones_twos
end
And your Two model looks like this:
class Two < ActiveRecord::Base
has_many :ones_twos, :dependent => :destroy
has_many :twos, :through => :ones_twos
end

acts_as_list with has_and_belongs_to_many relationship

I've found an old plugin called acts_as_habtm_list - but it's for Rails 1.0.0.
Is this functionality built in acts_as_list now? I can't seem to find any information on it.
Basically, I have an artists_events table - no model. The relationship is handled through those two models specifying :has_and_belongs_to_many
How can I specify order in this situation?
I'm assuming that you have two models - Artist and Event.
You want to have an habtm relationship between them and you want to be able to define an order of events for each artist.
Here's my solution. I'm writing this code from my head, but similar solution works in my case. I'm pretty sure there is a room for improvement.
I'm using rails acts_as_list plugin.
That's how I would define models:
class Artist < ActiveRecord::Base
has_many :artist_events
has_many :events, :through => :artist_events, :order => 'artist_events.position'
end
class Event < ActiveRecord::Base
has_many :artist_events
has_many :artists, :through => :artist_events, :order => 'artist_events.position'
end
class ArtistEvent < ActiveRecord::Base
default_scope :order => 'position'
belongs_to :artist
belongs_to :event
acts_as_list :scope => :artist
end
As you see you need an additional model ArtistEvent, joining the other two. The artist_events table should have two foreign ids and additional column - position.
Now you can use acts_as_list methods (on ArtistEvent model, unfortunately) but something like
Artist.find(:id).events
should give you a list of events belonging to specific artist in correct order.
Additional update for the accepted answer: for Rails 4 and Rails 5:
has_many :events, -> { order 'artist_events.position ASC' }, through: :artist_events
has_many :artists, -> { order 'artist_events.position ASC' }, through: :artist_events
I trying with self-referencing like that
class Product < ActiveRecord::Base
has_many :cross_sales
has_many :cross_sales_products, :through => :cross_sales, :order => 'cross_sales.position'
end
class CrossSale < ActiveRecord::Base
default_scope :order => 'cross_sales.position'
belongs_to :product
belongs_to :cross_sales_product, :class_name => "Product"
acts_as_list :scope => :product
end
create_table :cross_sales, :force => true, :id => false do |t|
t.integer :product_id, :cross_sales_product_id, :position
end
But the field cross_sales.position is never updated ...
An idea ?
Update: Ok the field 'id' it's necessary in the case of additional model with has_many :through option. It's work well now
In the accepted answer, note that :order => 'artist_events.position' is referencing the table artist_events and not the model.
I ran into this minor hiccup when moving from a habtm association to has_many :through.

Validate uniqueness of many to many association in Rails

Say I have Project, that is in many-to-many association with Tag. I'm using has_many through so I have separate join model.
How do I create validation, that checks uniqueness of join model? Now I have only
has_many :tags, :through => :taggings, :uniq => true
But that doesn't validate on save.
I think what you want is validates_uniqueness_of:
class Taggings
belongs_to :tags
validates_uniqueness_of :tag_id, :scope => :project_id
end
This is what I'm using, and works well.
Try validates_associated.
That should, I believe, allow the join model validations to run before saving. So in your case:
class Project
has many :tags, :through => :taggings
validates_associated :taggings
end
class Taggings
belongs_to :tags
#your validations here....
end
class Tag
has_many :taggings
end

Thinking Sphinx - RuntimeError: Missing Attribute for Foreign Key

Trying to get along with Sphinx/Thinking Sphinx for the first time.
I've got my models defined as follows (simplified):
class Branch < ActiveRecord::Base
has_many :salesmen, :class_name => "User"
has_many :leads, :through => :salesmen
end
class User < ActiveRecord::Base
belongs_to :branch
has_many :leads, :foreign_key => "owner_id"
end
class Lead < ActiveRecord::Base
belongs_to :owner, :class_name => "User"
define_index do
indexes company_name
indexes :name, :sortable => true
has owner.branch_id, :as => :branch_id
indexes [owner.last_name, owner.first_name], :as => :owner_full_name, :sortable => true
end
end
Anytime I call
Branch.first.leads.search
I get
RuntimeError: Missing Attribute for Foreign Key branch_id
What am I doing wrong?
The issue is that Thinking Sphinx needs branch_id as an attribute in your index, so it can restrict results to just the relevant branch (because you're searching within an association).
It's not clear from your associations (or maybe that just my dire need for sleep) whether a lead belongs to a branch via the owner, or directly as well. If the former, Ben's suggestion is probably correct. Otherwise, try adding the following to your define_index block:
has branch_id, :as => :direct_branch_id
An alternative approach, after reading the comments, is to add your own search method to the leads association in Branch. A vague attempt (you will need to debug, I'm sure):
has_many :leads, :through => :salesmen do
def search(*args)
options = args.extract_options!
options[:with] ||= {}
options[:with][:branch_id] = proxy_owner.id
args << options
Lead.search(*args)
end
end
This should get around the fact that you do not have a direct reference to the branch from Lead. The only possible issue is that I'm not sure whether custom extensions get loaded before or after what Thinking Sphinx injects. Give it a shot, see if it helps.
If you say a Lead belongs_to a Branch, then you must have a branch_id in the leads table. Since you don't, it's not a belongs_to relationship. I think you need something like this:
class Branch < ActiveRecord::Base
has_many :leads, :through => :salesmen
has_many :salesmen, :class_name => "User"
end
class User < ActiveRecord::Base
belongs_to :branch # users table has branch_id
has_many :leads, :foreign_key => "owner_id"
end
class Lead < ActiveRecord::Base
belongs_to :owner, :class_name => "User" # leads table has owner_id
define_index do
indexes :company_name
indexes :name, :sortable => true
has owner.branch_id, :as => :branch_id
indexes [owner.last_name, owner.first_name], :as => :owner_full_name, :sortable => true
end
end
I believe you're missing a :through option on your branch relation. Try updating to:
class Lead < ActiveRecord::Base
has_one :branch, :through => :owner

Resources