I have a form that I've created to capture simple contact information from a user:
views/whitepapers/index.html.erb
<%= form_tag({action: "download"}, id: "whitepaper-form-#{w.id}") do %>
<%= label_tag 'name' %>
<%= text_field_tag "contact[name]", nil, class: "form-control" %>
<br/>
<%= label_tag 'email' %>
<%= email_field_tag "contact[email]", nil, class: "form-control" %>
<br/>
<%= label_tag 'phone' %>
<%= text_field_tag "contact[phone]", nil, class: "form-control" %>
<%= hidden_field_tag 'id', w.id %>
<%= hidden_field_tag 'whitepaper-name', w.title %>
<%= submit_tag "Download Now", class: "btn btn-success", id: "whitepaper-# {w.id}-submit" %>
<% end %>
Now, Once the user clicks the "Download" button, the file downloads, so I have that part taken care of. Now I'd like to email the form data without saving anything to the DB.
I've created the mailer: mailers/whitepaper_download_mailer.rb
class WhitepaperDownloadMailer < ApplicationMailer
def email_lead(contact)
#contact = contact
mail to: "admin#example.co", subject: "A Whitepaper Download!"
end
end
And I've started working on implementing in the controller, but all the examples I've run across have to do with data including the model. This is what I have so far, but it's not working in my controller:
controllers/whitepapers.rb
def download
#whitepaper = Whitepaper.find(params[:id])
#contact.name = params[:contact_name]
#contact.email = params[:contact_email]
#contact.phone = params[:contact_phone]
#contact.whitepaper_name = params[:whitepaper_name]
file_path = File.join(Rails.root, "public", #whitepaper.whitepaper_url)
send_file file_path
WhitepaperDownloadMailer.email_lead(#contact).deliver_now
end
models/whitepaper.rb
class Whitepaper < ActiveRecord::Base
mount_uploader :whitepaper, WhitepaperUploader
validates :title, presence: true
validates :abstract, presence: true
validates :whitepaper, presence: true
end
Obviously, I know this isn't going to work since I'm passing #contact to the mailer, but pulling form params into a structure (i.e. #contact.name). Should I be passing each of the parameter variables into the mailer:
WhitepaperDownloadMailer.email_lead(#contact.name, #contact.email, #contact.phone).deliver_now
Or is there some other way that I haven't found yet to make this mailer work?
I figured this out with help from #kevinthompson and Openstruct. So, directly from the form, in my controller controllers/whitepapers.rb:
def contact
#whitepaper = Whitepaper.find(params[:contact][:whitepaper_id])
file_path = File.join(Rails.root, "public", #whitepaper.whitepaper_url)
send_file file_path
if request.post?
#contact = OpenStruct.new(params[:contact])
WhitepaperDownloadMailer.email_lead(#contact).deliver_now
end
end
I also ended up changing the form_tag action in the view to coincide:
<%= form_tag({action: "contact"}, id: "whitepaper-form-#{w.id}") do %>
Related
I got this error with my user registration form in Rails:
ActionView::Template::Error (First argument in form cannot contain nil or be empty)
View:
<%= form_for User.new, url: create_user_path, method: :post do |f| %>
<div class="form-group">
<%= f.label :name, t("settings.account.fullname"), class: "form-label" %>
<%= f.text_field :name, class: "form-control #{form_is_invalid?(User.new, :name)}", placeholder: t("settings.account.fullname"), autofocus: "", required: "" %>
<div class="invalid-feedback d-block"><%=User.new.errors.full_messages_for(:name).first %></div>
</div>
Controller:
def create
#user = User.new(user_params)
#user.provider = #user_domain
end
and so on..
I'm new to ROR. Can any one help me with this please?
Apart from the bizarre error message this is just not how you do forms in Rails.
Since you're passing User.new to the form it will always be bound to a new instance of User. That means that anything the user has entered into the form will blanked out on an invalid form submission. User.new.errors.full_messages_for(:name).first will give a nil error since there are no validation messages on a record that has not been validated.
What you actually want is something like:
# routes.rb
resources :users
class UsersController < ApplicationController
# GET /users/new
def new
#user = User.new
end
# POST /users
def create
#user = User.new(user_params)
#user.provider = #user_domain
# ...
end
# ...
end
<%= form_with(model: #user) do |form| %>
<div class="form-group">
<%= f.label :name, t("settings.account.fullname"), class: "form-label" %>
<%= f.text_field :name, class: "form-control #{form_is_invalid?(User.new, :name)}", placeholder: t("settings.account.fullname"), autofocus: "", required: "" %>
<% if #user.errors.has_key?(:name) %>
<div class="invalid-feedback d-block">
<%= #user.errors.full_messages_for(:name).each do |msg| %>
<p><%= msg %></p>
<% end %>
</div>
<% end %>
<% end %>
If you just follow the Rails conventions you do not need to specify the URL or the method which are derived from the record. This lets you reuse the same form for updating existing records without changing anything in your code.
create_user_path is in itself extremely unidiomatic as Rails doesn't have a separate path for creating records. You create records by sending a POST request to the collection path (/users).
I have this models where I wanna update my typestored columns using update_columns active record
I have searched numerous answers in stack, doesn't seems to have lot of resources regarding typestored.
show.html.erb
<%= form_tag print_booking_path(#booking), method: 'post' do %>
<%= label_tag :name %>
<%= text_field_tag :name, '', class: 'form-control' %>
<%= label_tag :age %>
<%= text_field_tag :age, '', class: 'form-control' %>
<%= submit_tag "Print", class: "btn btn-default" %>
<%= link_to 'Cancel', '#', class: 'btn btn-default', data: { dismiss: 'modal' } %>
<% end %>
bookings_controller
def print
#booking = Booking.find(params[:id])
if #booking.print_model(current_user, params[:name], params[:age])
render :print
else
render :print
end
end
booking model
def print_model(user, name_test, age_test)
self.update_columns(name: name_test, age: age_test)
end
typestore under booking model
typed_store :profile, coder: PostgresCoder do |i|
i.string :name, default: ""
i.integer :age, default: 0
end
the error appeared to be like this
can't write unknown attribute name
it's same like if I wanna to update like this self.increment!(:age)
I made a few tests here, and I don't know if it makes sense, but in case of updating stores, all the typestored columns are inside just one real column in the database (that are a normally in hash or Json).
So the straight foward way to do it is to wrap in brackets.
Try changing:
self.update_columns( name: name_test, age: age_test )
To:
self.update_columns( profile: { name: name_test, age: age_test} )
I'm learning Rails by following Hartl's tutorial and making my own adjustments to it. Now, I would like to extent it and add a contact form that sends an email message. Such is not included in the tutorial, but by the end of chapter 10 we're learned to use the mailer method and we've configured SendGrid on Heroku.
I already have the view set up in the routes and think it would require the following additional steps:
1) Terminal. rails generate mailer ContactForm
2) In app/mailers/contactform.rb:
def send_contactform_email(visitor)
#visitor = visitor
mail( :to => myemail#example.com, :from => visitor.email, :subject => 'Contact form' )
end
3) app/views/contactform_mailer/ (the view for the mail message) for example:
<h1>Website contact form</h1>
<p>On <$= ... %> <%= "#{#visitor.name} (#{#visitor.email} sent the following message:" %></p>
<p><%= #visitor.message %></p>
4) app\controllers\static_pages_controller (or another location?)
# Sends contact form email.
def send_contact_form_email
ContactFormMailer.send_contactform_email(visitor).deliver_now
redirect_to contact_path, notice: 'Message sent'
end
5) app\views\static_pages\contact.html.erb (I'm not sure about the first line, should I also do something in the routes.rb? My guess is this first line will have to tell to execute the method in step 4, which is not going to work the way it is now.)
<%= form_for(:static_pages, url: contactform_path) do |f| %>
<i class="pt-row-icon glyphicon glyphicon-user"></i> <%= f.label :name %>
<%= f.text_field :name, placeholder: 'Name', class: 'form-control' %>
<i class="pt-row-icon glyphicon glyphicon-envelope"></i> <%= f.label :email %>
<%= f.email_field :email, placeholder: 'Email', class: 'form-control' %>
<i class="pt-row-icon glyphicon glyphicon-envelope"></i> <%= f.label :message %>
<%= f.text_area :message, placeholder: 'Your message…', class: 'form-control' %>
<%= f.submit "Send", class: "btn btn-primary" %>
<% end %>
I don't think this is 100% correct yet, particularly the bold sections. What are your thoughts?
UPDATE, VERSION 2: I've tried to make the updates as suggested by Ven and now have the code below. The idea as I understand it is that
the controller in def contact sets the #message variable.
the form_for knows it should fill this variable with params[:message].
the controller adopts the values from the form_for and passes them to the mailer.
the mailer uses the mailer view to design the message to be sent.
the mailer sends it back to the controller that send the message.
1) App/controllers/static_pages_controller.rb
class StaticPagesController < ApplicationController
before_action :valid_email?, only: [:send_message_email]
# Shows the contact form page
def contact
#message = message(message_params)
end
# Sends the message.
def send_message_email
#message = message(message_params)
if #message.valid?
MessageMailer.new_message(#message).deliver_now
redirect_to contact_path, notice: "Your messages has been sent."
else
flash[:alert] = "An error occurred while delivering this message."
render :new
end
end
private
def message_params
params.require(:message).require(:name, :email, :content)
end
def valid_email?(email)
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
email.present? && (email =~ VALID_EMAIL_REGEX)
end
end
2) Contact form in app\views\static_pages\contact.html.erb:
<%= form_for(message: params[:message], url: contact_path) do |f| %>
<%= f.label :name %> <%= f.text_field :name, placeholder: 'Name', class: 'form-control' %>
<%= f.label :email %> <%= f.email_field :email, placeholder: 'Email', class: 'form-control' %>
<%= f.label :content %> <%= f.text_area :content, placeholder: 'Your message…', class: 'form-control' %>
<%= f.submit "Send", class: "btn btn-primary" %>
<% end %>
3) Routes.rb
get 'contact' => 'static_pages#contact', as: 'contact'
post 'contact' => 'static_pages#send_message_email'
4) App/views/message_mailer.text.erb (and html.erb)
<%= #message[:name] %> <%= #message[:email] %> wrote:
<%= #message[:content] %>
5) App/mailers/message_mailer.rb
class MessageMailer < ApplicationMailer
default to: "myemail#example.com>"
def new_message(message)
#message = message
mail to: "myemail#example.com"
mail from: #message[:email]
mail subject: "Message from #{message[:name]}"
end
end
Now when I try to visit the contact form on the server, I get the error message: param is missing or the value is empty: message. It refers to the params.require(:message).require(:name, :email, :content) line. Not sure what I'm doing wrong. Changing it to params.require(:message).permit(:name, :email, :content) makes no difference.
4) app\controllers\static_pages_controller (or another location?)
This seems to be correct, if this is the github repo for said app.
def send_contact_form_email
You controller has an issue: this action will try to send the email, not matter if it's used in POST or GET. You should use two different actions, one for displaying the view (using GET), and one for sending the email (using the mailer class you created). (at this point, you might want to create another controller)
ContactFormMailer.send_contactform_email(visitor).deliver_now
Then, moving on: what you pass to your mailer is "visitor". There's no such variable.
You probably want to access something out of the params hash (which contains parameters for GET and POST requests), and use the same key as your form (form_for(:visitor ... => params[:visitor] (so you want to change that :static_pages)).
<p>On <$= ... %> <%= "#{#visitor.name} (#{#visitor.email} sent the following message:" %></p>
As this returns an object, and not a hash, #visitor.email needs to be #visitor[:email] inside the mailer.
One last thing: simply using params[:visitor] will mean people could leave the field blanks. You might want to look into strong parameters, that were added in Rails 4 (the book seems somewhat outdated?).
And lastly, you need to add routes to be able to reach these actions (one for the GET request - display the view - and one for the POST request - to submit the form).
PS:
mail( :to => myemail#example.com, :from => visitor.email, :subject => 'Contact form' )
Warning: here, you forgot to quote the email address. Also, you swapped the to/from parameters. You want to send TO your visitor email, not from it.
EDIT
params.require(:message).require(:name, :email, :content)
This will require said keys, but AFAIK on the same "level" as :message - the top one. You want to use permit:
params.require(:message) # require "namespace"
.permit(:name, :email, :content) # permit keys
#message = message(message_params)
Where is the message function defined?
mail to: "myemail#example.com"
mail from: #message[:email]
mail subject: "Message from #{message[:name]}"
This sends 3 different emails, since you called the mail function 3 times.
I have a model called AlphaUser. When I submit a form to create a new alpha user, I'm told there wasn't any data in the params[:post]. How can this happen?
My code for the form:
<%= form_for :alpha_user,
url: alpha_users_path,
class: "form",
id: "alphaTest" do |f| %>
<div class="form-group">
<%= f.label :email %>
<%= f.text_field :email,
class: "form-control", placeholder: "grandma#aol.com" %>
<%= f.text_field :producer,
class: "hidden", value: "0" %>
</div>
<%= f.submit "Request Access", class: "btn btn-primary" %>
<% end %>
My controller:
class AlphaUsersController < ApplicationController
def create
render text: params[:post].inspect
end
end
Data is still recieved, just in this format:
{"utf8"=>"✓", "authenticity_token"=>"blahblahblah", "alpha_user"=>{"email"=>"test#test.com", "producer"=>"0"}, "commit"=>"Request Access", "action"=>"create", "controller"=>"alpha_users"}
You need to call params for the :alpha_user... e.g. params[:alpha_user].
If you're using strong_parameters in Rails 4, this should do the trick:
class AlphaUsersController < ApplicationController
def create
#alpha_user = AlphaUser.new(alpha_user_params)
#alpha_user.save
end
private
def alpha_user_params
params.require(:alpha_user).permit(:email, :producer)
end
end
Notice that in your create action you are no longer referencing the params directly, but the alpha_user_params method required by strong_parameters.
Check this answer: Custom name for params hash from Rails form_for
Also there's a reason for this naming convention. Look here through section 7 "Understanding Parameter Naming Conventions".
Forgive me for asking what i believe is quite an in depth challenge (well for me at the moment anyway)
I have a small app that allows users to check in, check out and hopefully receive emails when a book has been checked back in by registering their interest via a remind me button
So far I have setup actionmailer (basic setup)
class ReminderMailer < ActionMailer::Base
default from: "email address"
def remind_email(book)
#book = book
#url = "http://localhost:3000"
mail(:to => #book.user.email, :subject => "Book Reminder")
end
I have all the config in place to send the emails as I am already doing that through devise.
I have also created the mailer templates. It is the logic I am stuck with.
So when a User checks a book out i pass this back to the model
<%= form_for #book do |f| %>
<%= f.label :checked_out, "Check Book Out?" %>
<%= f.check_box :checked_out, {}, true %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.hidden_field :checked_out, :value => true %>
<%= f.submit 'Checkout' %>
<% end %>
Check In
<%= form_for #book do |f| %>
<%= f.label :checked_out, "Check Book Back In" %>
<%= f.check_box :checked_out, {checked: false}, false %>
<%= f.hidden_field :user_id, :value => nil %>
<%= f.hidden_field :checked_out, :value => false %>
<%= f.submit 'Check In' %>
<% end %>
Register Interest
<%= form_for #book do |f| %>
<%= f.label :remind_me, "let Me know when book back in" %>
<%= f.check_box :remind_me, {checked: false}, false %>
<%= f.hidden_field :remind_me, :value => current_user.id %>
<%= f.submit 'Remind Me' %>
<% end %>
So my thinking is that when you register your interest your user id gets placed into the remind_me column, and what i want to achieve is that when the checked_out field is false and book.user_id is back to nil I would like the email to send the the user whos user_id is in the remind_me column
Am i thinking about this in the correct way?
if anyone can help it would be appreciated so that i can learn from this and then keep practicing it until I understand what is going on
There are 2 ways to answer:
The first one, don't use a form to check in a book and just call a method. For example: You replace your form with a link which call a new method in your controller:
<%= link_to "check in", check_in_book_path(#book.id) %>
In your books_controller you call a model method which check in the book:
def check_in
#book = Book.find params[:id]
#book.check_in!
redirect_to book_path(#book)
end
In your book model:
def check_in!
self.user = nil
self.checked_out = false
if self.save
RemindMailer.remind_mail(self).deliver
end
end
Don't forget to add the route for your new controller method.
The second way, if you keep your form, is shorter but more complicated. You need to add a callback to your model which will verify if the data changed. For example, in you book model:
after_save :send_mail_if_check_in
def send_mail_if_check_in
if !self.checked_out && self.changes[:user_id] && self.user.nil?
RemindMailer.remind_mail(self).deliver
end
end
I prefer the first solution because it seems to be a state machine which is more maintenable.
I hope this help