Ruby on Rails: Validate contact form without model - ruby-on-rails

I have a simple contact form that accepts the following fields (all should be required): Name, Email, phone, and message.
I also want to validate the email address.
Users should be given a response on whether or not the form submitted successfully, or if there are errors.
if so, display specific errors on the view.
This form is not connected to any database model. I'm not saving submissions. Only mailing.
I have a POST route set to contact_form in my PagesController
In my PagesController I have
def contact_form
UserMailer.contact_form(contact_form_params).deliver
end
In my UserMailer class I have:
def contact_form(params)
#formParams = params;
#date = Time.now
mail(
to: "support#example.com",
subject: 'New Contact Form Submission',
from: #formParams[:email],
reply_to: #formParams[:email],
)
end
This mails successfully, but there's no validation. I need to only run the mail block if validation passes. then return a response to the user.
Since I have no Model, I'm not sure how to do this. All the answers I see tell people to use validates on the ActiveRecord model.
With the few answers:
(note I've updated my params)
class UserMailerForm
include ActiveModel::Validations
def initialize(options)
options.each_pair{|k,v|
self.send(:"#{k}=", v) if respond_to?(:"#{k}=")
}
end
attr_accessor :first_name, :last_name, :email, :phone, :message
validates :first_name, :last_name, :email, :phone, :message, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
end
def contact_form
#form = UserMailerForm.new(contact_form_params)
if #form.valid?
UserMailer.contact_form(contact_form_params).deliver
else
logger.debug('invalid')
logger.debug(#form.valid?)
end
end
This sends mail when valid. However, I'm still unsure about sending info to the user

You can make UserMailer a model and use validations on that.
class UserMailer
include ActiveModel::Model # make it a model
include ActiveModel::Validations # add validations
attr_accessor :name, :email, :phone, :message
validates :name, :email, :phone, :message, presence: true
validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
def send_mail(subject:, to:)
mail(
to: to,
subject: subject,
from: email,
reply_to: email,
)
end
end
Then use it like any other model.
def UserMailersController < ApplicationController
def new
#user_mailer = UserMailer.new
end
def create
#user_mailer = UserMailer.new(params)
if #user_mailer.valid?
#user_mailer.send_mail(
to: "support#example.com",
subject: 'New Contact Form Submission',
)
else
# Use #user_mailer.errors to inform the user of their mistake.
render 'new'
end
end
end
If you have multiple forms associated with UserMailer, you can make separate classes to validate each Form's inputs and then pass them along to UserMailer. You'd likely still want validations on UserMailer regardless.

You can use ActiveModel::Validations on your PORO the same way AR does this.
class MyFormObject
include ActiveModel::Validations
def initialize(options)
options.each_pair{|k,v|
self.send(:"#{k}=", v) if respond_to?(:"#{k}=")
}
end
attr_accessor :name, :email, :phone, :message
validates :name, presence: true
# and so on...
end

Related

How can I validate the object's attribute and values exist?

My goal is to validate my form and making sure all fields filled out in my form exists and not blank. if ALL these condition's attributes first_name, last_name, date_of_birth exists AND values are not blank it can search the database; otherwise, it just return to the search page with an error telling the user that all fields need to be filled out. Im doing this validation for my backend.
I currently having this object received from filling out a form in my view:
pry(#<RegistrantsController>)> #q.conditions
=> [Condition <attributes: ["first_name"], predicate: matches, values: ["John"]>, Condition <attributes: ["last_name"], predicate: matches, values: ["Smith"]>]
As you see I haven't filled out the date_of_birth in my form that's why it is not in this array but basically that's why I want to validate this.
how can I loop through and implement this condition?
Use the validations provided by ActiveModel just like you would in normal CRUD actions:
class SearchForm
include ActiveModel::Model
include ActiveModel::Attributes
attribute :first_name, :string
attribute :last_name, :string
attribute :date_of_birth, :date
validates :first_name, :last_name,
:date_of_birth, presence: true
end
class SomeController
def search
#search_form = SearchForm.new(search_params)
if #search_form.valid?
# do something
else
flash.now[:error] = "Oh noes..."
render :some_kind_of_view
end
end
private
def search_params
params.require(:q)
.permit(:first_name, :last_name, :date_of_birth)
end
end
In the model is where you should do your validations.
class Model
validates :first_name, :last_name, presence: true
end
then is the controller for that model you should be able to create a conditional:
def create
#user = User.new(user_params)
if #user.save
//....
else
render json: #user.errors.full_messages, status: 422
end
end
def user_params
params.require(:user).permit(:first_name, :last_name, :date_of_birth)
end

Two models with one controller using devise, problems with validations and error messages

I have two models in my app, one 'user' model with devise, one 'employee' model made by scaffolding.
I need a way to have the employee table populated as soon as a new user registers, both tables share some params, some are exclusive. The employee belongs to the user model, each user has one employee.
The view I use is the devise user registration form with nested attributes to allow for the employee params. Creation is handled by the user controller.
Problems that occur:
undefined method 'email' for empty password field until the first user is created
cannot make the error message for the exclusive 'name' parameter of the employee model show up in the same place as the error messages for the user model
employee-model:
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
user model
class User < ApplicationRecord
audited
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable
has_one :employee, inverse_of: :user
accepts_nested_attributes_for :employee
validates_presence_of :password
validates :password, format: { with: /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}, if: -> {password.present?}
validates_presence_of :password_confirmation, if: -> {password.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_confirmation_of :password, if: -> {password.present? and password_confirmation.present? and password =~ /(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[["§#?!`´~#'._,;<>|°()"{}="#$%^&*+-]]).{8,}/}
validates_presence_of :email
validates :email, format: { with: /\A([^#[0-9]\s]+\.)+([^#[0-9]\s]+)#((thisapp+\.)+com)\z/i}, uniqueness: true, if: -> {email.present?}
end
user controller
class Users::RegistrationsController < Devise::RegistrationsController
# before_action :configure_sign_up_params, only: [:create]
# before_action :configure_account_update_params, only: [:update]
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
# POST /resource
def create
#employee = Employee.new(employee_params)
super
#employee.email = User.last.email
#employee.user_id = User.last.id
#employee.created_by_id = User.last.id
#employee.save
end
user registration view
.card style='text-align:center;'
.card-body
h2.card-title style='text-align:center;' = t('.sign_up')
= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f|
= render "users/shared/error_messages", resource: resource
br
.field
= f.label :email, t('users.email_field')
br
= f.text_field :email, autofocus: true, autocomplete: "email"
.field
= f.label :password, t('users.password_field')
/- if #minimum_password_length
em
= t('devise.shared.minimum_password_length', count: #minimum_password_length)
br
= f.password_field :password, autocomplete: "new-password"
.field
= f.label :password_confirmation, t('users.password_confirmation_field')
br
= f.password_field :password_confirmation, autocomplete: "new-password"
= fields_for :employee do |e|
= e.label :name
= e.text_field :name
So, when I use #employee.email = User.last.email in the controller I get a 'unknown method 'email' error when not filling out the password field, unless I have a preexisting user, then I get my custom error messages for not filling out the email. I guess it is because I am looking for a last user who does not exist at this point. Could seed a user, but that seems hackish. Tried using #employee.email = User.last(params[:email]) which leads to the email being saved as some hash value, but at least I get my error messages. Is there a way to convert that hash to the real email address again?
The other issue is the validation of the name field. Validation is asked for in the employee model, and user model accepts nested attributes, but that does not seem to be enough.
I did
if params[:employee][:name].blank?
flash[:notice] = t('.noname')
which works insofar as that the form cannot be submitted without some value in the name field, but messes up my error messages. Shows a flash message where all other errors (no email/password/pw confirmation) are handled by the devise's shared error messages as non-flash messages:
- resource.errors.full_messages.each do |message|
li
= message
So having the blank name as a flash message would look inconsistent, and the spot for the flash message is already reserved.
Flash message is on top, 'following errors prevent..', actual error messages are below 'Registrieren', and that is also where the error message for blank name would need to be.
Any ideas on how to approach this or maybe a better solution than handling this stuff in the user controller?
class Users::RegistrationsController < Devise::RegistrationsController
# GET /resource/sign_up
def new
super
end
def new_employee
#employee = Employee.new
end
def sign_up_params
params.require(:user).permit(:email, :password, :password_confirmation, employee_attributes: %i[name])
end
This signup param will be used while creating a user from devise registration controller. Since we used nested attributes, passing the arguments along with the parent object will handle employee creation(https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html)
For assigning email for employee
The one way is to merge the email from the user params.
def sign_up_params
sign_up_param = params.require(:user).permit(:email, :password, :password_confirmation,employee_attributes: %i[name])
sign_up_param[:employee_attributes].merge!(email: params[:user][:email])
sign_up_param
end
Or maybe you could assign the email of the employee from the user before the validation.
class Employee < ApplicationRecord
audited
validates :email, presence: true, uniqueness: true
validates :name, presence: true
belongs_to :user, inverse_of: :employee, dependent: :destroy, optional: true
# Callbacks
before_validation :set_email
# Methods
# Set email of the employee from the user.
def set_email
self.email = self.user.email
end

Rails - Object attributes accessible individually but not by inspect method

Sorry I'm new to rails but can't wrap my head around this one.
I have an Order object with various attributes - no references
In my controller I can print out the attributes individually via their attr_accessor and see them in the console via puts.
But when I call .inspect they are all nil! any suggestions?
class Order < ApplicationRecord
attr_accessor :name, :email, :phone, :date, :dessert_type, :size, :quantity, :dessert, :comments, :total
validates :name, :date, :quantity, presence: true
validates :quantity, numericality: { only_integer: true, greater_than: 0}
validate :contact_provided?
private
def contact_provided?
if :email.blank? || :phone.blank?
errors.add(:base, "Please provide either phone or email so we can contact you!")
end
end
end
Controller
def create_order
puts "create_order object"
#order = Order.new order_params
if #order.valid?
puts #order.inspect
#everything is null here
#order.attributes.each do |attr_name, attr_value|
puts "#{attr_name}: #{attr_value}"
end
#this prints out fine!
puts "dessert: #{#order.dessert}"
end
end
Parameters
Parameters: {"utf8"=>"✓", "authenticity_token"=>"randomtoken", "order"=>{"name"=>"jim", "email"=>"test#email.com", "phone"=>"12345678", "dessert_type"=>"Cake", "size"=>"25.0", "dessert"=>"Chocolate Caramel", "date"=>"2018-04-15", "quantity"=>"1", "comments"=>""}, "commit"=>"Submit Order"}
Any insight much appreciated!
That's because this line:
attr_accessor :name, :email, :phone, :date, :dessert_type, :size, :quantity, :dessert, :comments, :total
is overriding the Order attributes in the way Rails works with them. As working with Rails you don't need that declaration, so you can remove them as attr_accessor.
As Order is an ActiveRecord model, then the getters and setters are already generated by ActiveRecord for all of your object attributes.
What you're doing right now is defining all of your attributes, with the attr_accessor as virtual attributes which are attributes on the model that don't persist in the database, just "an attribute not corresponding to a column in the database".

Rails validation from controller

There is a contact page, which offers to enter name, telephone, email and message, after that it sends to an administrator's email. There is no reason to store message in DB.
Question. How to:
Use Rails validations in controller, not using model at all, OR
Use validations in model, but without any DB relations
UPD:
Model:
class ContactPageMessage
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :telephone, :email, :message
validates :name, :telephone, :email, :message, presence: true
validates :email, email_format: { :message => "Неверный формат E-mail адреса"}
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
controller:
def sendmessage
cpm = ContactPageMessage.new()
if cpm.valid?
#settings = Setting.first
if !#settings
redirect_to contacts_path, :alert => "Fail"
end
if ContactPageMessage.received(params).deliver
redirect_to contacts_path, :notice => "Success"
else
redirect_to contacts_path, :alert => "Fail"
end
else
redirect_to contacts_path, :alert => "Fail"
end
end
end
you should use model without inheriting from ActiveRecord::Base class.
class ContactPageMessage
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :whatever
validates :whatever, :presence => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
Through this you will able to initialize new object and able to call validations on that object.
I think you have a different class name with same name, in your controller code, I can see this :
if ContactPageMessage.received(params).deliver
redirect_to contacts_path, :notice => "Success"
else
if this is your mailer class change its name to ContactPageMessageMailer. you will no loger get that error.
Hope it will help. Thanks
I would still advice you to use model, rails models doesn't have to inherit from ActiveRecord::Base.
For example:
class Contact
include ActiveModel::Validations
attr_accessor :name, :telephone, :email, :message
validates_presence_of :name, :telephone, :email, :message
validates_format_of :email, with: EMAIL_REGEXP
end
and you can use it in your controller with:
contact = Contact.new
# ...
if contact.valid?
# do something
else
# do something else
end
In your model you can add the below which will just set getter and setter method for message and you can have validation on message without having a column in the db
attr_accessor :message
validates :message, presence: true

Rails: sending emails, getting undefined method `model_name' for Mail::Message:Class

My Rails skills is (to be kind) rusty so this is probably a newbie question. I'm trying to create a simple email sending form, but I keep getting:
NoMethodError in Mail#create
undefined method `model_name' for Mail::Message:Class
I'm pretty sure that my problem is in my controller, the relevant method looks like this:
def create
#mail = Mail.new(params[:mail])
MailMailer.send_mail(#mail).deliver
end
Thinks this line is causing the error #mail = Mail.new(params[:mail]). My Mail model class looks like this:
class Mail < ActiveRecord::Base
include ActiveModel::Validations
validates :password, :presence => true
attr_accessor :password, :to, :cc, :bcc, :from, :subject, :message
end
And my mailer looks like this:
class MailMailer < ActionMailer::Base
def send_mail(mail)
mail(:to => mail.to, :subject => mail.subject)
end
end
Can anyone spot what I'm doing wrong?
Your problem is probably right here:
class Mail < ActiveRecord::Base
# ---------^^^^^^^^^^^^^^^^^^
include ActiveModel::Validations
validates :password, :presence => true
attr_accessor :password, :to, :cc, :bcc, :from, :subject, :message
end
Subclassing ActiveRecord::Base and including ActiveModel::Validations is a bit odd as AR already includes all the validation stuff. Mixing AR and attr_accessor is another sign that something strange is going on.
In your comments you note that you created this model with:
$ rails g model mail
And that tries to create a database-backed ActiveRecord model as that's almost always what people want. You might also run into trouble because Mail is already in use so maybe you want to use a different name.
If you just want a model that is just a temporary bag of data then you can do this:
class SomeEmail
attr_accessor :password, :to, :cc, :bcc, :from, :subject, :message
end
You probably don't need the validations here but you can add them:
class SomeEmail
include ActiveModel::Validations
validates :password, :presence => true
attr_accessor :password, :to, :cc, :bcc, :from, :subject, :message
end
but the validations won't be triggered unless you manually call valid? so there's no much point.
Finally, just adding attr_accessor doesn't give you a useful constructor so with all of the above changes, this:
#mail = SomeMail.new(params[:mail])
still won't do what you want as nothing in params[:mail] will get saved anywhere. So add an initialize implementation to your email class and a call to valid? to your controller.

Resources