Rails 4.2: using deliver_later with a tableless model - ruby-on-rails

I am trying to setup a contact form using Rails 4.2's deliver_later method. However, I can only get deliver_now to work, as deliver_later is trying to serialize my object and fails each time.
Here's my setup:
messages_controller.rb
class MessagesController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
ContactMailer.contact_form(#message).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(msg)
#message = msg
mail(:subject => msg.subject, from: msg.email)
end
end
message.rb
class Message
include ActiveModel::Model
include ActiveModel::Conversion
## Not sure if this is needed ##
include ActiveModel::Serialization
extend ActiveModel::Naming
attr_accessor :name, :subject, :email, :body
validates_presence_of :email, :body
validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates_length_of :body, :maximum => 1000
def initialize(attributes = {})
attributes.each { |name, value| send("#{name}=", value) }
end
## Not sure if this is needed ##
def attribtues
{'name' => nil, 'subject' => nil, 'email' => nil, 'body' => nil}
end
end
The error I get when calling ContactMailer.contact_form(#message).deliver_later is:
ActiveJob::SerializationError in MessagesController#create
Unsupported argument type: Message
Extracted source (around line #10):
if #message.valid?
ContactMailer.contact_form(#message).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
Ideally I'd like this to be a background process. I will be adding something like Sidekiq soon but I think it's best I get this serialization problem fixed beforehand.
Any help is appreciated! Thanks :)

In order to use your class with ActiveJob (that's what deliver_later delegates to), it needs to be able to uniquely identify the object by its ID. Further, it needs to find it later by the ID when deserializing (no manual deserialize is necessary in the mailer / job).
class Message
...
include GlobalID::Identification
...
def id
...
end
def self.find(id)
...
end
end
ActiveRecord would provide you with these methods but since you're not using it, you need to implement it yourself. It's up to you to decide where you want to store the record but honestly I think you'd be better off by using ActiveRecord and the table underneath.

A simple solution that avoids having to back the object with ActiveRecord or create an unnecessary table:
Instead of passing the Message object to the contact_form method, you can also pass the message params to the contact_form method and then initialize the Message object inside that method.
This will solve the problem without having to create a table, because you are initializing the object in the delayed job worker's memory space.
For example:
messages_controller.rb
MessagesController < ApplicationController
def new
#message = Message.new
end
def create
#message = Message.new(params[:message])
if #message.valid?
ContactMailer.contact_form(params[:message]).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(msg_params)
#message = Message.new(msg_params)
mail(:subject => msg.subject, from: msg.email)
end
end

I had a similar problem today and solved it as follows.
Convert a tableless object into a JSON sting
Pass it to a mailer
Convert the json string to hash
Environment
Rails 5.0.2
messages_controller.rb
class MessagesController < ApplicationController
# ...
def create
#message = Message.new(message_params)
if #message.valid?
ContactMailer.contact_form(#message.serialize).deliver_later
redirect_to root_path, notice: "Message sent! Thank you for contacting us."
else
render :new
end
end
# ...
end
contact_mailer.rb
class ContactMailer < ApplicationMailer
default :to => Rails.application.secrets['email']
def contact_form(message_json)
#message = JSON.parse(message_json).with_indifferent_access
mail(subject: #message[:subject], from: #message[:email])
end
end
message.rb
class Message
include ActiveModel::Model
attr_accessor :name, :subject, :email, :body
validates_presence_of :email, :body
validates_format_of :email, with: /\A([^\s]+)((?:[-a-z0-9]\.)[a-z]{2,})\z/i
validates_length_of :body, :maximum => 1000
# Convert an object to a JSON string
def serialize
ActiveSupport::JSON.encode(self.as_json)
end
end
Hope this will help anybody.

You'll need to serialize the object before passing to AJ and deserialize in the mailer.

Related

uninitialized constant UController::User_param

I am making a basic account setup and to try to learn how the database stuff works. I have been running into this error constantly, and I have no idea how to make it disappear. I have my stuff named as U, so the URL will be easier to type a username like Reddit has it example.com/u/username
The Error is uninitialized constant UController::User_param
It highlights this code: #user = U.new(User_param)
Controller:
class UController < ApplicationController
def index
#users = U.all
end
def show
end
def create
#user = U.new(User_param)
if #user.save
redirect_to :action => 'list'
else
#user = U.all
render :action => 'new'
end
end
def User_param
params.require(:Us).permit(:id, :email, :password, :created_at, :updated_at)
end
def new
#user = U.new
end
def edit
end
end
Routes:
resources :u
U Model:
class U < ActiveRecord::Base
end
In Rails you don't capitalize methods, only constants and classes. change User_param to user_params along with the method and that should work. I made params plural since it is clearer and easier to understand
Also, change the user_param method to this:
def user_params
params.require(:u).permit(:id, :email, :password, :created_at, :updated_at)
end
The .require(:u) doesn't need to be plural as you had it.

Upload base64 Image with Paperclip - Rails 4

I am relatively new to Rails and would appreciate any help.
My website accepts a signature image in base64 format and I am trying to use a Paperclip adaptor to decode the image and save it to my form model as the :signature attribute. I am using the advice given here (and here) which advises to use the following code:
In model:
class Thing
has_attached_file :image
In controller:
def create
image = Paperclip.io_adapters.for(params[:thumbnail_data])
image.original_filename = "something.gif"
Thing.create!(image: image)
...
end
My assumption is that Thing.create! is setting the value of Paperclip's model attribute :image to be the value of the image variable whilst creating and saving a new Thing object. I tried to implement the same code in my FormsController (create action) before #form.save, but am receiving this error:
undefined method `before_image_post_process' for #<Class:0x007f94a2a26de8>
My FormsController:
class FormsController < ApplicationController
before_action :logged_in_user
before_action :admin_user, only: :destroy
def index
#forms = Form.all #paginate
end
def show
#form = Form.find(params[:id])
end
def new
#form = Form.new
end
def create
#form = Form.new(form_params)
# Paperclip adaptor
signature = Paperclip.io_adapters.for(params[:base64])
signature.original_filename = "something.png"
# Attempt to submit image through Paperclip
#form.signature = signature
if #form.save
flash[:success] = "The form has been successfully created!"
redirect_to #form
else
render 'new'
end
end
def edit
#form = Form.find(params[:id])
end
def update
#form = Form.find(params[:id])
if #form.update_attributes(form_params)
flash[:success] = "Form has been updated!"
redirect_to #form
else
render 'edit'
end
end
def destroy
Form.find(params[:id]).destroy
flash[:success] = "Form deleted"
redirect_to forms_path
end
private
def form_params
params.require(:form).permit(:first_name, :last_name, :email, :phone, :address, :member_type, :base64)
end
end
This is my Form model:
class Form < ActiveRecord::Base
has_attached_file :signature
validates_attachment_content_type :image, :content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]
end
Assuming that you're using the Rails form helpers over in your view, and based on your form_params list, the :base64 key won't be at the top level of your params hash, but rather one level down at params[:form][:base64]

Accept JSON in Rails Controller when wrong format

I have the following in my Registrations Controller:
class Api::V1::RegistrationsController < ApplicationController
skip_before_filter :verify_authenticity_token
respond_to :json
def create
user = User.new(params[user_params])
if user.save
render :json => user.as_json(:auth_token=>user.authentication_token, :email=>user.email), :status=>201
return
else
warden.custom_failure!
render :json => user.errors, :status=>422
end
end
def user_params
params.require(:user).permit(:email, :password, :name, :phone, :acknowledgement)
end
end
I realize this expects my JSON to be in the form of
{user:{email:user#example.com,name:"anotheruser"}}
However the JSON is being sent as
{email:user#example.com, name:"anotheruser"}
I don't know how to target those params. What is the syntax for that?
Also, is there a special way to handle that format?
The expected json you got would be a hash of hashes, but you are only getting back a hash, is what it sounds like.
Given this, you don't need to require :user since :user isn't there. Just do this.
params.permit(:email, :password, :name, :phone, :acknowledgement) if params.present?
present? will check to ensure params is not nil or blank/empty. You could raise an error, if you wanted, if the present? check fails.

Rails 4 - railscast #154 polymorphic association

Im following ryan bates screen cast on polymoprhic associations
http://railscasts.com/episodes/154-polymorphic-association-revised?view=asciicast
I've done this sucessfully before on a Rails 3 app but now im on Rails 4 and i feel like im having issues with strong parameters..... but i can be wrong
when i go into my console to create a new event for a user it works
a = Event.first
c = a.events.create!(name: "Hello World")
this works and posts on my events index page
howwever when i try to use the actual form on the site it creates the record but the name field is nil and blank...and i dont get any errors
heres my controller (im basically just copying what Ryan Bates does on the site)
class EventsController < ApplicationController
before_filter :load_eventable
def index
#eventable = Admin.find(params[:admin_id])
#events = #eventable.events
end
def new
#event = #eventable.events.new
end
def create
#event = #eventable.events.new(params[:events])
if #event.save
redirect_to [#eventable, :events], notice: "Event created."
else
render :new
end
end
private
def load_eventable
resource, id = request.path.split('/')[1,2]
#eventable = resource.singularize.classify.constantize.find(id)
end
def events
params.require(:events).permit(:name, :address, :city, :state, :zip, :date, :time, :admin_id)
end
end
here my form (very simple and im just using name for now)
= form_for [#eventable, #event] do |f|
.field
= f.text_field :name
= f.submit
Try the below: creating a new event with the event_params method you defined instead of the params hash. I changed the name to make it a little less confusing.
class EventsController < ApplicationController
...
def create
#event = #eventable.events.new(event_params)
if #event.save
redirect_to [#eventable, :events], notice: "Event created."
else
render :new
end
end
private
...
def event_params
params.require(:events).permit(:name, :address, :city, :state, :zip, :date, :time, :admin_id)
end
end

How to validate foreign keys in Rails validations?

In my Rails app I have users who can have many projects which in turn can have many invoices.
How can I make sure that a user can only create an invoice for one of his projects and not for another user's projects?
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :project_id
validates :project_id, :presence => true,
:inclusion => { :in => ????????? }
end
Thanks for any help.
class InvoicesController < ApplicationController
def new
#invoice = current_user.invoices.build(:project_id => params[:project_id])
end
def create
#invoice = current_user.invoices.build(params[:invoice])
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
end
I think that shouldn't be on a validation. You should ensure the project the user selected is one his projects.
You could do something on your controller like:
project = current_user.projects.find params[:project_id]
#invoice = Invoice.new(project: project)
# ...
Your create action could look something like this.
def create
#invoice = current_user.invoices.build(params[:invoice])
#invoice.project = current_user.projects.find params[:invoice][:project_id]
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
project_id is "sensitive" attribute - so remove it from attr_accessible. You are right that you should not believe params from the form and you must check it.
def create
#invoice = current_user.invoices.build(params[:invoice])
# #invoice.project_id is nil now because this attr not in attr_accessible list
#invoice.project_id = params[:invoice][:project_id] if current_user.project_ids.include?(params[:invoice][:project_id])
if #invoice.save
flash[:success] = "Invoice saved."
redirect_to edit_invoice_path(#invoice)
else
render :new
end
end
If user tries to hack your app and change project_id to not owned value then method create render partial new with invalid #invoice. Do not forget to leave the validation of project_id on presence.
If you get exception Can't mass-assign protected attributes... there are several ways what to do. The simplest ways are:
1. remove line from environment configs (development, test, production)
# Raise exception on mass assignment protection for Active Record models
config.active_record.mass_assignment_sanitizer = :strict
2. Reject sensitive parameters from params before assigning.
# changes in method create
def create
project_id = params[:invoice].delete(:project_id)
#invoice = current_user.invoices.build(params[:invoice])
#invoice.project_id = project_id if current_user.project_ids.include?(project_id)
...
end
OK, luckily I managed to come up with a solution of my own this time.
I didn't make any changes to my controller ("let's keep 'em skinny"), but added a validation method to my model instead:
class Invoice < ActiveRecord::Base
attr_accessible :number, :date, :project_id
validates :project_id, :presence => true,
:numericality => { :only_integer => true },
:inclusion => { :in => proc { |record| record.available_project_ids } }
def available_project_ids
user.project_ids
end
end
I am not sure if this is good or bad coding practice. Maybe someone can shed some light on this. But for the moment it seems pretty safe to me and I haven't been able to hack it in any way so far.

Resources