Validate uniqueness of many to many association in Rails - ruby-on-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

Related

Tables for Articles, dealers and units

I have 3 Models:
Article (:title)
has_many :units
Dealer (:name, :adress)
has_many :units
Unit (:price, :dealer_id, :article_id)
belongs_to :article
belongs_to :dealer
I'm not sure if I'm perfectly right with my tables and I'm not sure if I should use has_many :through or just has:many and belongs to? What is exactly the difference?
And how exactly would the rails queries look like?
Article.find(:name => "Cheese").units
Article.find(:name => "cheese").units.minimum('price').dealer
Would such complex rails queries work in this kind of relationsship?
You could declare the has_many through like this:
Unit (:price, :dealer_id, :article_id)
belongs_to :article
belongs_to :dealer
Article (:title)
has_many :units
has_many :dealers, through: :units
Dealer (:name, :adress)
has_many :units
has_many :articles, through: :units
Using the has_many :objects, through: :relation let you access to the objects of a particular entry:
#dealer.articles
# and
#article.dealers
This has_many through: is a different way to do a has_and_belongs_to_many: it allows additional attributes and validations on the join model (Ruby style Guide)
Doing the following will generate 2 queries to the DB:
Article.find(:name => "Cheese").units
We know that the Unit model has an attribute article_id. The best in this case would be (assuming you know the article's id):
Unit.where(article_id: article.id)
This generate only one query to the DB.

Active Record has_many :through remove one associated record

This may be a very basic oversight on my part, but I can't seem to recall a simple method for removing an association between two objects joined via has_many :through. IE:
class Photo
has_many :tags, :through => :taggings
has_many :taggings, :dependent => :destroy
end
class Tags
has_many :photos, :through => :taggings
has_many :taggings, :dependent => :destroy
end
class Taggings
belongs_to :photo
belongs_to :tag
end
If you have two objects, tag and photo, you can associate them just by doing this:
photo.tags << tag
So, is there an equally simple opposite to this? ie:
photo.tags.remove tag
here's what you want:
photo.tags.delete(tag)

How to establish associations for a model with two belongs_to relationships?

I am building an application with the following model functions
Groups have many Users
Groups have many Expenses (each expense has a :name, :total, :added_by_user_id fields)
Expenses have many owings (1 for each user in the group)
Owings have an :amount and a :user_id, to reference which user the owing is referring
So far, I have set up the models as followings:
# user.rb
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name, :email, :password
has_many :memberships, :foreign_key => "member_id", :dependent => :destroy
has_many :groups, :through => :memberships
has_many :owings
end
# group.rb
class Group < ActiveRecord::Base
attr_accessible :name
has_many :memberships, :dependent => :destroy
has_many :members, :through => :memberships
has_many :expenses
end
# expense.rb
class Expense < ActiveRecord::Base
attr_accessible :total_dollars, :name, :owings_attributes, :added_by_user_id
belongs_to :group, :inverse_of => :expense
has_many :owings, :dependent => :destroy
end
# owing.rb
class Owing < ActiveRecord::Base
attr_accessible :amount_dollars, :user_id
belongs_to :expense, :inverse_of => :owings
belongs_to :user, :inverse_of => :owings
end
# NB - have left off memberships class (and some attributes) for simplicity
To create an expense, I'm using #group.expenses.build(params[:expenses]), where params come from a nested model form that includes attributes for the owings that need to be created. The params include the 'user_id' for each of the 'owing' instances for that expense.
I have two concerns:
Firstly - I've made 'user_id' accessible in the owings model, meaning that a malicious user can change who owes what in an expense (I think?). I don't know how to get around this, though, because the user needs to see the names of all the other members of the group when they fill out the expense/owings form.
Secondly - I've also made 'added_by_user_id' accessible in the expense model - I also wouldn't want malicious users to be able to change this, since this user_id has special edit/delete priveleges for the expense. Is there some clever way to make an expense 'belong_to' a User AND a group, and set both of these associations when creating WITHOUT having to make either an accessible attribute? If it helps, the 'added_by_user_id' can always be set to the current_user.
Any ideas? Very possible I'm missing something fairly fundamental here.
Thanks in advance!
PS. Long time listener, first time caller. Thanks to all of you for teaching me ruby on rails to date; this website is an incredible resource!
Have you thought about setting them dynamically?
dynamic attr-accessible railscast

Ruby on Rails ActiveRecord "has_many :through" uniqueness validation

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.

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.

Resources