Uniqueness field using Scope of association - ruby-on-rails

I want to insure that an items name is unique within an organization. So I've used the "validates_uniqueness_of :name, scope: [:organization]" within Item. This unfortunetely didn't work.
Error (edited):
> 1) Item
> Failure/Error: #item_1 = create(:item, :item_category => #item_cat_1)
>
> NoMethodError:
> undefined method `organization_id' for #<Item:0x00000002565840>
models:
class Item < ActiveRecord::Base
belongs_to :item_category
has_one :organization, through: :item_category
validates_uniqueness_of :name, scope: [:organization]
end
class ItemCategory < ActiveRecord::Base
has_many :items
belongs_to :organization
end
class Organization < ActiveRecord::Base
has_many :item_categories
has_many :items, :through => item_categories
end
In theory, as I did above may I use the item's, item_category association (belongs_to :item_category) for the organization_id?
If the above isn't possible. I guess I could have an organization_id in the item and the item_category. But then how could we validate that a item.organization_id be always equal to an item_category.organization_id (its association)

It is okay to not include the organization_id inside an item?
Yes, not include because the column organization_id will be redundant.
For complex validation, we usually use customized one to validate, here is my example, you may correct it:
class Item < ActiveRecord::Base
belongs_to :item_category
has_one :organization, through: :item_category
# validates_uniqueness_of :name, scope: [:organization]
validate :check_uniqueness_of_name
def check_uniqueness_of_name
if Organization.includes(item_categories: :items).where.not(items: {id: self.id}).where(items: {name: self.name}).count > 0
errors.add(:name, 'name was duplidated')
end
end
end

Related

act_as_list with intermediate table

I'm trying to use act_as_list to order some items, but I'm having troubles with the scope, I want the scope to be the group of the item, but I have no idea how to do it. This is what I have now:
class Group < ActiveRecord::Base
has_many :types
has_many :items, through: :types
end
class Type < ActiveRecord::Base
belongs_to :group
has_many :items
end
class Item < ActiveRecord::Base
belongs_to :type
acts_as_list scope: ?
end
I tried with scope: [type: :group] but it doesn't work, I tried with:
has_one :group, through: :type
and
def group_id
type.group.id
end
But then I got an error with PG because the column group_id doesn't exists...
Any idea?

Validate relationship using associated table

I have the following models:
class Property < ActiveRecord::Base
belongs_to :property_type
has_many :variant_properties
has_many :variants, through: :variant_properties
end
class PropertyType < ActiveRecord::Base
has_many :properties
end
class Variant < ActiveRecord::Base
has_many :variant_properties
has_many :properties, through: :variant_properties
end
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validates_uniqueness_of :property, scope: :property_type
end
What I am trying to validate is that two Properties for the same Variant should never belong to the same Property_Type.
Is there any way to perform this validation in a Rails way?
EDIT:
Finally solved using a custom validator, as suggested by #qaisar-nadeem. A redundant column would be also ok, but I would consider it an optimization more than a solution.
class Property < ActiveRecord::Base
(...)
validate :property_type_uniqueness
private
def property_type_uniqueness
unless property_type_unique?
msg = 'You cannot have multiple property variants with same property type'
errors.add(:property_id, msg)
end
end
def property_type_unique?
VariantProperty
.where(variant: variant)
.select { |vp| vp.property.property_type == property.property_type }
.empty?
end
end
Validates scope cannot access joined table so you will need to have custom validation.
So there are two options.
Option 1 : Use a custom validator and have SQL check if there is any Property Variant with same Property Type.
Custom Validations guide can be found on http://guides.rubyonrails.org/active_record_validations.html#custom-validators
Option 2 : Add a redundant column property_type_id in variant_properties model and then add validates_uniqueness_of :property, scope: :property_type as you already have done
UPDATE
Here is the custom validator
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
validate :check_property_type_uniqueness
def check_property_type_uniqueness
errors.add(:property_id, "You cannot have multiple property variants with same property type") if VariantProperty.joins(:property).where(:property_id=>self.property_id,:variant_id=>self.variant_id,:properties=>{:property_type_id=>self.property.property_type_id}).count > 0
end
end
your should add relation with PropertyType to VariantProperty
class VariantProperty < ActiveRecord::Base
belongs_to :property
belongs_to :variant
has_one :property_type, through: :property
validates :property_type, uniqueness: { scope: :variant_id }
end

FactoryGirl with has_many :through associations with capybara

I have the following models:
class Productmainclass < ActiveRecord::Base
attr_accessible :name, :id, :maintext
has_many :producttaggings, :dependent => :destroy
has_many :products, :through => :producttaggings
has_many :productsubclasses
end
class Productsubclass < ActiveRecord::Base
attr_accessible :name, :id, :maintext
has_many :producttaggings, :dependent => :destroy
has_many :products, :through => :producttaggings
belongs_to :productmainclass
end
class Product < ActiveRecord::Base
attr_accessible :name, :productimage, :size, :description, :price
has_many :producttaggings, :dependent => :destroy
has_many :productsubclasses, :through => :meteoritetaggings
has_many :productmainclasses, :through => :meteoritetaggings
mount_uploader :productimage, ProductimageUploader
end
class Producttagging < ActiveRecord::Base
belongs_to :product
belongs_to :productsubclass
belongs_to :productmainclass
attr_accessible :product_id, :productsubclass_id, :productmainclass_id
end
I now want to create a Product with FactoryGirl and Capybara. In the spec I simply have:
product = FactoryGirl.create(:product)
In my factories.rb I have:
factory :product do
name "Blue glass"
description "Description text of product"
productimage File.new(File.join(::Rails.root.to_s, "spec/factories/", "testimage.jpg"), 'rb')
productsubclass
productmainclass
end
factory :productsubclass do
name "Colored glasses"
productmainclass
end
factory :productmainclass do
name "Glasses"
end
Running the test I get:
Failure/Error: product = FactoryGirl.create(:product)
NoMethodError:
undefined method `productsubclass=' for #<Product:0xcd42090>
I think the way you have it setup would work if you were dealing with a situation where :product belonged to productsubclass, then the product and the productsubclass would be created with the product.productsubclass_id nicely inserted and all would be fine, but that's clearly not your structure, so we'd have to use another way. I think the link that #depa noted is the right way to go, specifically the 'Basic has many associations' section in this document: http://robots.thoughtbot.com/aint-no-calla-back-girl although you have the added complexity of a has_many through. But essentially your looking at a situation where you create an object and then after that you trigger another create to make the many's. Hope this makes sense :)
** Update **
Here's another approach which might be a little limited but you could just create the records from the other direction. So, if you just want one record in each object/table how about this:
FactoryGirl.define do
factory :producttagging do
product
productsubclass
productmainclass
end
end

find_or_create on a has many through relationship

I have a has many through relationship in my app:
Shows has many Bands through => Lineups
Bands are unique by :name
class Show < ActiveRecord::Base
attr_accessible :city_id, :title, :dateonly, :timeonly, :image, :canceled, :venue_attributes, :bands_attributes
belongs_to :city
belongs_to :venue
has_many :lineups
has_many :bands, through: :lineups
has_and_belongs_to_many :users
end
class Lineup < ActiveRecord::Base
belongs_to :show
belongs_to :band
end
class Band < ActiveRecord::Base
attr_accessible :name, :website, :country, :state
has_many :lineups
has_many :shows, through: :lineups
validates :name, presence: true
validates_uniqueness_of :name
before_save :titleize_name
private
def titleize_name
self.name = self.name.titleize
end
end
New Bands are created like this:
(lets say we have a show record already saved called s1)
> s1.bands.new(name: "Wet Food")
> s1.save
Right now this will only save if a band named "Wet Food" doesn't already exist
In which model is the best place to do a Band.find_or_create in this relationship so that an existing band can be used if one with the same name exists?
This is generally the type of call that would go in a Controller (or maybe a service object), but not in a Model. It really depends on the particular user flow that you're trying to accomplish in your app. Basically, where ever you are already using s1.bands.new, you could use this instead :
s1.bands.where(name: 'Wet Food').first_or_create

rails creating model with multiple belongs_to, with attr_accessible

My models look something like this:
class User < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Product < ActiveRecord::Base
attr_accessible: :name
has_many :reviews
end
class Review < ActiveRecord::Base
attr_accessible: :comment
belongs_to :user
belongs_to :product
validates :user_id, :presence => true
validates :product_id, :presence => true
end
I am trying to figure out what the best way is to create a new Review, given that :user_id and :product_id are not attr_accessible. Normally, I would just create the review through the association ( #user.reviews.create ) to set the :user_id automatically, but in this case I am unsure how to also set the product_id.
My understanding is that if I do #user.reviews.create(params), all non attr_accessible params will be ignored.
You can do:
#user.reviews.create(params[:new_review])
...or similar. You can also use nested attributes:
class User < ActiveRecord::Base
has_many :reviews
accepts_nested_attributes_for :reviews
...
See "Nested Attributes Examples" on http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html.
It seems you would like to implement a many-to-many relationship between a User and Product model, with a Review model serving as a join table to connect the two with an added comment string. This can be accomplished with a has many through association in Rails. Start by reading the Rails Guides on Associations.
When setting up your Review model, add foreign keys for the User and Product:
rails generate model review user_id:integer product_id:integer
And set up your associations as follows:
class User < ActiveRecord::Base
has_many :reviews
has_many :products, through: :reviews
end
class Product < ActiveRecord::Base
has_many :reviews
has_many :users, through: :reviews
end
class Review < ActiveRecord::Base
# has comment string attribute
belongs_to :user
belongs_to :product
end
This will allow you to make calls such as:
user.products << Product.first
user.reviews.first.comment = 'My first comment!'
Here's how you would create a review:
#user = current_user
product = Product.find(params[:id])
#user.reviews.create(product: product)

Resources