Let it start from the beginning.
We want our registration form object to have the same API as any other ActiveRecord model:
// view.html <%= form_for(@book) do |f| %> <% end %>
To do this, we create the following object:
class RegForm include ActiveModel::Model attr_accessor :company_name, :email, :password def save # Save Location and Account here end end
Now, having ActiveModel::Model , our RegForm gets a lot of functionality, including error display and attribute checking (yes, there is no need to include ActiveModel::Validations ). In the next step, we will add some checks:
validates :email, :password, presence: true
And we change save to perform these checks:
def save validate
validate returns true if all checks pass and false otherwise.
validate also adds errors to @reg_form (all ActiveRecord models have an errors hash that populates when validation fails). This means that in the view we can do any of them:
@reg_form.errors.messages #=> { email: ["can't be blank"], password: ["can't be blank"] } @reg_form.errors.full_messages #=> ["Email can't be blank", "Password can't be blank"] @reg_form.errors[:email] #=> ["can't be blank"] @reg_form.errors.full_messages_for(:email) #=> ["Email can't be blank"]
Meanwhile, our RegistrationsController should look something like this:
def create @reg_form = RegForm.new(reg_params) if @reg_form.save redirect_to @reg_form, notice: 'Registration was successful' else render :new end end
We can clearly see that when @reg_form.save returns false , the new view re-displays.
Finally, we change save so that our save calls are wrapped inside transaction :
def save if valid? ActiveRecord::Base.transaction do location = Location.create!(location_params) account = location.create_account!(account_params) end true end rescue ActiveRecord::StatementInvalid => e
Points worthy of attention:
create! used instead of create . transaction only goes back if an exception occurs (which methods are usually executed using bang).
validate just an alias for valid? . To avoid all this indentation, we could instead use the security feature at the top of the save method:
return if invalid?
We can turn the database exception ( as limiting email uniqueness ) into an error by doing something like:
rescue ActiveRecord::RecordNotUnique errors.add(:email, :taken) end
We can add an error not directly related to the attribute using the :base symbol:
errors.add(:base, 'Company and Email do not match')