validates specific attribute of associated model - ruby

What's the simplest railsy way to validate an attribute of an associated model?
Item
belongs_to :user
validates_presence_of :user
# AND the "is_photographer" column for that user must be true
User
has_many :items
# can be a regular user or a photographer

validate :user_is_photographer, :if => :user
def user_is_photographer
errors.add(:user, "should be a photographer") unless user.is_photographer
end

Related

Attempting to create a database item using the has_one relationship, no exceptions, but still no item

Models:
A User has_one Ucellar
A Ucellar belongs_to User
I have confirmed from multiple sources that these are set up correctly. For posterity, here is the top portion of those two models.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
This is actually the entire Ucellar model.
class Ucellar < ActiveRecord::Base
belongs_to :user
end
Ucellar has a column called user_id, which I know is necessary. The part of my application that creates a user uses the method create_with_oath. Below is the entire User class. Note the second line of the create method.
class User < ActiveRecord::Base
has_many :authorizations
has_one :ucellar
validates :name, :email, :presence => true
def create
#user = User.new(user_params)
#ucellar = #user.create_ucellar
end
def add_provider(auth_hash)
# Check if the provider already exists, so we don't add it twice unless authorizations.find_by_provider_and_uid(auth_hash["provider"], auth_hash["uid"])
Authorization.create :user => self, :provider => auth_hash["provider"], :uid => auth_hash["uid"]
end
end
def self.create_with_omniauth(auth)
user = User.create({:name => auth["info"]["name"], :email => auth["info"]["email"]})
end
private
def user_params
params.require(:user).permit(:name, :email)
end
end
EDIT:
Forgot to summarize the symptoms. On create, the user is in the db, with no exceptions thrown, and nothing to signify that anything went wrong. However, the related ucellar is never created. Per the documentation Here, the create method should create AND save the related ucellar.
It should create ucellar too.
Try to get the error messages after the creation by calling:
raise #user.errors.full_messages.to_sentence.inspect
I'm not sure why this wasn't working, but I ended up just moving this code out of the create action of the user controller, and putting it directly after an action that was creating a user. It solved my issue though. Thanks everyone for your help!

Many to Many relationship :through giving 'Could not find the association' error

In my model an Item is created by a User and can be purchased by many Users, and a User can purchase many Items.
User, Item, and Purchase are defined, using AcvtiveRecord with superfluous details snipped for brevity as follows:
class User < ActiveRecord::Base
# various other fields
has_many :items, :foreign_key => :creator_id
has_many :purchased_items, :through => :purchases, :source => :item
end
class Item < ActiveRecord::Base
# various other fields
belongs_to :creator, :class_name => 'User'
has_many :buyers, :through => :purchases, :source => :user
end
class Purchase < ActiveRecord::Base
belongs_to :item
belongs_to :user
# various other fields
end
and an rspec test also snipped as follows:
describe "user purchasing" do
it "should allow a user to purchase an item" do
a_purchase = Purchase.create!(:item => #item, # set up in `before :each`
:user => #user # set up in `before :each`
)
a_purchase.should_not eq(nil) # passes
#item.buyers.should include #user # fails
#user.purchased_items.should include #item # fails
end
end
This results in
1) Purchase user purchasing should allow a user to purchase an item
Failure/Error: #item.buyers.should include #user
ActiveRecord::HasManyThroughAssociationNotFoundError:
Could not find the association :purchases in model Item
Likewise if I swap around #file_item.buyers.should include #user and #user.purchased_items.should include #item I get the equivalent
1) Purchase user purchasing should allow a user to purchase an item
Failure/Error: #user.purchased_items.should include #item
ActiveRecord::HasManyThroughAssociationNotFoundError:
Could not find the association :purchases in model User
My migration looks like
create_table :users do |t|
# various fields
end
create_table :items do |t|
t.integer :creator_id # file belongs_to creator, user has_many items
# various fields
end
create_table :purchases do |t|
t.integer :user_id
t.integer :item_id
# various fields
end
What have I done wrong?
You have to specify the following.
class User < ActiveRecord::Base
has_many :purchases
has_many :items, :foreign_key => :creator_id
has_many :purchased_items, :through => :purchases, :source => :item
end
class Item < ActiveRecord::Base
# various other fields
has_many :purchases
belongs_to :creator, :class_name => 'User'
has_many :buyers, :through => :purchases, :source => :user
end
Only when you specify
has_many :purchases
the model will be able to identify the association.

has_many with two or more tables.

I'm trying to add multiple has_many relationships to my model:
class Program < ActiveRecord::Base
has_many :courses, :program_offers
belongs_to :university
attr_accessible :end_date, :name, :period, :start_date, :symbol, :year, :university_id, :description, :titles, :profile, :price
end
But I get:
hash expected error.
How can I reference two has many tables?
You can't do that because it is association method which takes only one association name as argument:
has_many(name, options = {}, &extension)
So specify each association in single line.
has_many :courses
has_many :program_offers
If you specify like that it considers that you are specifying some condition or block. See the API doc http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_many

Comparing two instantances of the same activerecord object returns false if attr_accessible is present Ruby

Using Ruby 1.9.3 and ActiveRecord 3.2.6.
I'm having an issue when trying to compare an ActiveRecord Object that has attr_accessible :property set on it, that is contained in an Array of associated Objects using include?(object).
These are my 2 ActiveRecord models, Account and Role.
Account:
class Account < ActiveRecord::Base
# Associations
#
has_many :role_assignments, :dependent => :destroy
has_many :roles, :through => :role_assignments
end
Role:
class Role < ActiveRecord::Base
attr_accessible :title
# Associations
#
has_many :role_assignments, :dependent => :destroy
has_many :accounts, :through => :role_assignments
end
If I then create a couple of Roles (say "Admin" and "Editor") and assign the "Admin" one to an Account, I would assume this would work:
role = Role.find_by_title("Admin")
account = Account.first # => The Account we assigned the "Admin" role to
account.roles.include?(role) # => Should be true but returns false
But this actually returns false!
If I remove the 'attr_accessible :title' from the Role model and repeat the above then it does return true.
So I guess my question is... why would attr_accessible cause this particular issue? or is it a case that I have to do my check to see if role does exist in account.roles a different way?
You could try that
account.role_ids.include?(role.id)

Skip validation for some members in associated models during create/update

I have the following 4 models
Hotel (name)
has_one :address
has_one :contact
has_one :bank_account
validates_presence_of :name
def build_dependencies
build_contact
build_address
build_bank_account
end
Address (phone, street_address, hotel_id)
belongs_to :hotel
validates_presence_of :phone, :street_address
Contact (name, email, hotel_id)
belongs_to :hotel
validates_presence_of :name, :email
BankAccount (name, number, hotel_id)
belongs_to :hotel
validates_presence_of :name, :number
In a form used to create a Hotel, I take input for both name and email for the Contact model but only phone for the Address model.
HotelController#new
#hotel = Hotel.new
#hotel.build_dependencies #this creates empty Contact and Address to generate the form fields
#render the form to create the hotel
HotelController#create
#receive form data
#hotel = Hotel.new
#hotel.build_dependencies
#hotel.save :validate => false
#hotel.attributes = params[:hotel]
#hotel.save :validate => false
This is the only way I was able to create a Hotel with contact information, phone from address and empty bank account. I had to call
#hotel.save :validate => false
the first time to save the Hotel instance with blank instances of BankAccount, Address, Contact. Then I had to update_attributes on contact and address and then
#hotel.save :validate => false
to ensure that the original form data got saved completely as expected.
This, beyond doubt, is a very bad piece of code. Can anyone tell me how to clean this up?
You can use a filter, namely the after_create to call the associated model to create associated records after saving of the #hotel variable.
I happen to be facing the same problem and here's my solution. Sorry this probably is not helpful after such a long while but it'll be good for future users.
class User < ActiveRecord::Base
has_one :bank_account
# Filter -- After Creation
after_create :create_default_bank_account
def create_default_bank_account
self.build_bank_account
self.bank_account.save(:validate=>false)
# :validate=>false must be called on the bank_account's save. I made the mistake of trying to save the user. =(
end
This way, you can take the empty row creation out of the create action, which IMHO, should belong to the Model. You can then use the edit action to let users "create" the bank account entry. Using this, you only need a standard create action (e.g. generated by rails g scaffold_controller).
Update: I re-read your question after answering and found myself to be more confused. I assume you want to render a form where the user is not expected to enter the bank account immediately but edit it later on another page.
Address
validates_presence_of :phone, :on => :create, :if => proc { |u| u.creating_hotel? }
validates_presence_of :street, :phone, :on => :update
Contact
validates_presence_of :name, :email :on => :update
def creating_hotel?
addressable_type == 'Hotel'
end
The user sees the :street, :name, :email fields only after the hotel is created and :phone during the creation.

Resources