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

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.

Related

Rails 4 many to many relationship

I have an app that has 2 models that need a many to many relationship. I originally tried using has_and_belongs_to_many but it wasn't working. After scrummaging through the internet I was convinced that this was not what I needed anyhow. So, I switched to a has_many through method. However, this isn't working either. I am now at a major loss on what the issue is. I have ripped it out many times and tried tutorial after tutorial but to no avail.
The models I am trying to connect are Message and Author. In my most recent attempt, I created a joining model (with corresponding table) called Authoring. Here is my code:
message.rb
has_many :authorings
has_many :authors, through: :authorings
author.rb
has_many :authorings
has_many :messages, through: :authorings
authoring.rb
belongs_to :message
belongs_to :author
messages_controller.rb
def messages_params
params.require(:message).permit(:title, :summary, :date, :duration, :youversion, :hours, :minutes, :seconds, :authors)
end
My Messages form:
<%= f.label :speakers %><br />
<%= collection_select(:authors, :authors, Author.all, :id, :name, {}, {:multiple => true, :size => 10}) %>
When I created a new message, this showed in the log:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"OcLeke/eRRzwApoMSgQF81kBCm7TaGxW1PoVAUgEcvo=", "message"=>{"title"=>"Test1", "summary"=>"This is my test summary.", "date(2i)"=>"8", "date(3i)"=>"25", "date(1i)"=>"2014", "date(4i)"=>"01", "date(5i)"=>"00", "hours"=>"0", "minutes"=>"58", "seconds"=>"52", "youversion"=>""}, "authors"=>{"authors"=>["", "1"]}, "commit"=>"I'm Finished", "id"=>"1"}
The form obviously recognizes the authors in the form but it doesn't save them in the database. What is my disconnect? Please help!!!
Thanks!
authors records are not getting created because the controller is expecting authors to be present within message key's value in params hash BUT if you notice the params hash in the log
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"OcLeke/eRRzwApoMSgQF81kBCm7TaGxW1PoVAUgEcvo=",
"message"=>{...}, "authors"=>{...}, "commit"=>"I'm Finished",
"id"=>"1"}
you would see that authors is coming as an altogether separate key in params hash which is why it is being ignored.
What you need to do is associate the authors with the form object, instead of using collection_select you need to use f.collection_select as shown below. Specify the first argument as :author_ids in f.collection_select.
<%= f.collection_select(:author_ids, Author.all, :id, :name, {}, {:multiple => true, :size => 10}) %>
As you have :multiple => true option specified, you would receive authors as an Array in the params hash. You need to permit author_ids as an Array in message_params method
def messages_params
params.require(:message).permit(:title, :summary, :date, :duration, :youversion, :hours, :minutes, :seconds, :author_ids => [])
end

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!

How to work with an instance of a model without saving it to mongoid

The users of my Rails app are receiving a lot of emails (lets say they represent signups from new customers of my users). When an email is received a customer should be created, and the email should be saved as well. However, if the customer already exists (recognized by the email address of the email), the email email should not be saved to the database. I thought this was handled by Email.new, and then only save if the email address is recognized. But it seems that Email.new saves the record to the database. So how do I work with an email before actually deciding wether I want to save it?
Example code:
class Email
include Mongoid::Document
field :mail_address, type: String
belongs_to :user, :inverse_of => :emails
belongs_to :customer, :inverse_of => :emails
def self.receive_email(user, mail)
puts user.emails.size # => 0
email = Email.new(mail_address: mail.fetch(:mail_address), user: user) # Here I want to create a new instance of Email without saving it
puts user.emails.size # => 1
is_spam = email.test_if_spam
return is_spam if is_spam == true
is_duplicate = email.test_if_duplicate(user)
end
def test_if_spam
spam = true if self.mail_address == "spam#example.com"
end
def test_if_duplicate(user)
self.save
customer = Customer.create_or_update_customer(user, self)
self.save if customer == "created" # Here I want to save the email if it passes the customer "test"
end
end
class Customer
include Mongoid::Document
field :mail_address, type: String
belongs_to :user, :inverse_of => :customers
has_many :orders, :inverse_of => :customer
def self.create_or_update_customer(user, mail)
if user.customers.where(mail_address: mail.mail_address).size == 0
customer = mail.create_customer(mail_address: mail.mail_address, user: user)
return "created"
end
end
end
I'm going to suggest a somewhat fundamental reworking of your function. Try rewriting your function like this:
class Email
def self.save_unless_customer_exists(user, mail)
email = Email.new(
mail_address: mail.fetch(:mail_address),
user: user
)
return if email.customer or email.is_spam? or email.is_duplicate?
Customer.create!(user: user)
email.save!
end
end
You won't be able to drop that code in and expect it to work, because you'd have to define is_spam? and is_duplicate?, but hopefully you can at least see where I'm coming from.
I'd also recommend writing some automated tests for these functions if you haven't already. It will help you pin down the problem.

validates specific attribute of associated model

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

activerecord validation - validates_associated

I'm unclear on what this method actually does or when to use it.
Lets say I have these models:
Person < ...
# id, name
has_many :phone_numbers
end
PhoneNumber < ...
# id, number
belongs_to :person
validates_length_of :number, :in => 9..12
end
When I create phone numbers for a person like this:
#person = Person.find(1)
#person.phone_numbers.build(:number => "123456")
#person.phone_numbers.build(:number => "12346789012")
#person.save
The save fails because the first number wasn't valid. This is a good thing, to me. But what I don't understand is if its already validating the associated records what is the function validates_associated?
You can do has_many :phone_numbers, validate: false and the validation you're seeing wouldn't happen.
Why use validates_associated then? You might want to do validates_associated :phone_numbers, on: :create and skip validation on update (e.g. if there was already bad data in your db and you didn't want to hassle existing users about it).
There are other scenarios. has_one according to docs is validate: false by default. So you need validates_associated to change that.

Resources