ActiveJob issue implementing Delayed_job - ruby-on-rails-5

For my personal website build with Rails 5, I'm trying to implement a job for sending a contact form. I'm using the delayed_job_active_record gem.
I generated a job ContactMessageJob:
app/jobs/contact_message_job.rb
class ContactMessageJob < ApplicationJob
queue_as :contact_message
def perform(message)
MessageMailer.send_message(message).deliver_now
end
end
Then in the MessagesController I'm calling the perform_later method on ContactMessageJob:
app/controllers/messages_controller.rb
...
def create
#message = Message.new(message_params)
if #message.valid?
ContactMessageJob.perform_later(#message)
redirect_to contact_path, notice: "Your message has been sent."
else
flash[:alert] = "An error occured while delivering this message."
render :new
end
end
but when I'm trying to send a message through the contact form, I getting this error when executing ContactMessageJob.perform_later(#message):
ActiveJob::SerializationError at /contact
Unsupported argument type: Message
I'm a bit new to using ActiveJob, so I'm not sure how I can fix this. Any suggestions are very welcome.
my repo is at: https://github.com/acandael/personalsite/tree/job
greetings,
Anthony

Rails' ActiveJob does serialization using GlobalID and GlobalID know how to serialize primitive values (string, number) and ActiveRecord objects. Your Message class is an basic model which GID cannot serialize.
Probably easier option for you is to pass to the job the message_params directly and use them from the job itself
MessageMailer.send_message(message_params[:name], message_params[:email], message_params[:content]).deliver_later
*) You don't need the job class on contact_message_job.rb as you can send async email using the deliver_later method

Related

Delayed_job ActionMailer caching?

I currently call a delayed_job (gem delayed_job_mongoid) to send a confirmation email. However, it doesn't seem to use the latest data I'm passing, and instead uses a cached version. See below:
My controller:
...
_user.calculate_orders
_user.save
_user.reload
Mailer.delay.order_reported(_user)
...
The Mailer
class Mailer < Devise::Mailer
def order_reported(to_user)
#to_user = to_user
email_with_name = "#{#to_user.name} <#{#to_user.email}>"
mail(:to => email_with_name, :subject => "Test email")
end
end
For example, if an attribute _user.total_orders = 3 gets updated to 5 and saved. It's correctly reflected in the database, the delayed job DB record contains the updated info of 5, but when email is sent, it uses cached info of 3.
I've also tried calling the method via rails console:
This works and uses the parameter that's passed and updated info
Mailer.order_reported(u).deliver
This doesn't work and uses cached data
Mailer.delay.order_reported(u)
I had a very similar problem with Sidekiq recently, related to passing in a serialized version of my User object instead of just an ID.
I would refactor slightly to only pass simple objects as parameters in the delayed method. So, you could do this instead:
Mailer.delay.order_reported(_user.id)
and then update the method to read as follows:
class Mailer < Devise::Mailer
def order_reported(to_user_id)
#to_user = User.find(to_user_id)
email_with_name = "#{#to_user.name} <#{#to_user.email}>"
mail(:to => email_with_name, :subject => "Test email")
end
end
That would ensure you always get a fresh user object when your mailer is actually delivered.

How to rescue custom exceptions coming from a middleware in Rails 3.2?

I have a Rails 3.2 application that uses Apartment, which is used as a middleware. Apartment throws an Apartment::SchemaNotFound exception and there is no way to rescue it with rescue_from from the ApplicationController. I thought I'd use config.exceptions_app as described in point #3 in this blog post, but I can't set the router as the exception app, I assume I have to create my own.
So the question is: How do I proceed?
We've purposefully left Apartment pretty minimal to allow you the handle the exception itself without really needing any Rails specific setup.
I'd do something similar to what #jenn is doing above, but I wouldn't bother setting a rack env and dealing with it later, just handle the response completely in rack.
It's typical for instance that you maybe just want to redirect back to / on SchemaNotFound
You could do something like
module MyApp
class Apartment < ::Apartment::Elevators::Subdomain
def call(env)
super
rescue ::Apartment::TenantNotFound
[302, {'Location' => '/'}, []]
end
end
end
This is a pretty raw handling of the exception. If you need something to happen more on the Rails side, then #jenn's answer should also work.
Check out the Rack for more details
I had a similar problem with another piece of middleware throwing a custom exception, so I haven't actually looked at Apartment at all, but maybe something like this:
#app/middleware/apartment/rescued_apartment_middleware.rb
module Apartment
class RescuedApartmentMiddleware < Apartment::Middleware
def call(env)
begin
super
rescue Apartment::SchemaNotFound
env[:apartment_schema_not_found] = true # to be later referenced in your ApplicationController
#app.call(env) # the middleware call method should return this, but it was probably short-circuited by the raise
end
end
end
end
Then in your environment:
config.middleware.use(Apartment::RescuedApartmentMiddleware, etc)
To access the env variable you set from ApplicationController or any controller:
if request.env[:apartment_schema_not_found]
#handle
end
Combo of How to rescue from a OAuth::Unauthorized exception in a Ruby on Rails application? and How do I access the Rack environment from within Rails?

Rails 3 + PostgreSQL + RSpec: App works fine but RSpec example fails

I am developing this Rails 3.2 application using the Apartment gem as middleware. The application itself works perfectly and all the RSpec examples also work perfectly when ran individually. However, when I run all the tests at the same time using the bundle exec rspec command, there are two examples that fail in two different controller specs and they do exactly the same thing. Here are the two examples in question:
In the issues_controller_spec.rb file:
describe "GET 'new'" do
# ...
context "for authenticated users" do
before(:each) do
controller.log_in(create(:user))
get :new
end
# ...
it "should create a new issue instance and put it in an instance variable" do
assigns(:issue).should be_an_instance_of Issue
end
end
end
In the users_controller_spec.rb file:
describe "GET 'new'" do
# ...
context "for authenticated users" do
# ...
context "for admin users" do
before(:each) do
admin = create(:admin)
admin.add_role :admin
controller.log_in(admin)
get :new
end
# ...
it "should create a new User instance and put it in an instance variable" do
assigns(:user).should be_an_instance_of User
end
end
end
end
These two examples are affected by a before hook:
before(:each) do
client = create(:client)
#request.host = "#{client.account_name}.lvh.me"
end
When creating a new Client, there is an after_create callback:
# Create the client database (Apartment) for multi-tenancy
def create_client_database
begin
Apartment::Database.create(self.account_name)
rescue Apartment::SchemaExists
return
rescue
self.destroy
end
end
And there is where the examples fail. Now if I remove the begin...rescue...end block and keep the line Apartment::Database.create(self.account_name) I get the following exception in the failling examples:
ActiveRecord::StatementInvalid:
PG::Error: ERROR: current transaction is aborted, commands ignored until end of transaction block
: SET search_path TO public
Again, if I run the examples individually, they pass but if I run all the examples, the two examples above fail.
Does anyone know what I am doing wrong please?
Note: The whole application code can be found here.
I solved this problem by wrapping the line client = create(:client) in a begin, rescue, end block like so:
before(:each) do
begin
client = create(:client)
rescue
client = Client.create!(attributes_for(:client))
end
#request.host = "#{client.account_name}.lvh.me"
end
I don't know how or why this works but I know it works.

Delayed job with custom attributes

I'm using delayed job 3.0.2 with ActiveRecord and Rails 3.2.3. I have a User model which uses the has_secure_password mixin, so the password is only stored encrypted. Now I want to use delayed job to send the welcome email, which should contain a copy of the unencrypted password.
When creating the record, the plain-text password is in User#password. But delayed job seems to serialize/ deserialize the id of the record only and create a new instance of the model by doing User.find(X). This way my plain-text password is lost and the user gets an empty password in his email.
How can I tell delayed-job to serialize/ deserialize custom "virtual" attributes too, which are not stored in the database otherwise?
This is my monkey patch for delayed job 2.x, which worked fine.
class ActiveRecord::Base
def self.yaml_new(klass, tag, val)
klass.find(val['attributes']['id']).tap do |m|
val.except("attributes").each_pair{ |k, v| m.send("#{k}=", v) }
end
rescue ActiveRecord::RecordNotFound
raise Delayed::DeserializationError
end
end
It doesn't work with delayed job 3.x. I'm also not really interested in fixing my monkey patch as I hope there's a proper solution to this.
In delayed job 3.x, the best way to do this is to override a few methods on your ActiveRecord class, and then to force the Psych YAML deserializer to load the ActiveRecord object from the serialized data. By default, delayed job uses just the deserialized id, and then loads the ActiveRecord object from the DB. So, say I have an ActiveRecord class called ShipmentImport, and I want an attr_accessor named 'user_id' to work with delayed job serialization/deserialization. Here is what I would do.
In the ShipmentImport ActiveRecord class, add this:
def encode_with(coder)
super
coder['user_id'] = #user_id
end
def init_with(coder)
super
#user_id = coder['user_id']
self
end
In an initializer for your application, add this for your ActiveRecord class:
Psych.load_tags[['!ruby/ActiveRecord', ShipmentImport.name].join(':')] = ShipmentImport

Help debugging my session? Rails 3 ActionDispatch::Cookies::CookieOverflow

Even though I'm pretty sure I know why this error gets raised, I don't seem to know why or how my session is exceeding the 4KB limit...
My app was working fine, but once I deliberately started adding bugs to see if my transactions were rolling back I started getting this error.
To give some background, I'm busy coding a tournament application that (in this section) will create the tournament and then add some tournament legs based on the number of teams as well as populate the the tournament with some 'ghost fixtures' once the legs have been created.
The flash[:tournament] was working correctly before; using a tournament object, I have access to any AR validation errors as well as data that has been entered on the previous page to create the tournament.
TournamentController.rb
begin
<other code>
Tournament.transaction do
tournament.save!
Tournament.generate_legs tournament
Tournament.generate_ghost_fixtures tournament
end
flash[:notice] = "Tournament created!"
redirect_to :action => :index
rescue Exception => e
flash[:tournament] = tournament
redirect_to :action => :new, :notice => "There was an error!"
end
Tournament.rb
self.generate_ghost_fixtures(tournament)
<other code>
#Generate the ghost fixtures
#tournament_legs is a has_many association
tournament_legs_array = tournament.tournament_legs
tournament_legs_array.each do |leg|
number_of_fixtures = matches[leg.leg_code]
#For the first round of a 32 team tournament, this block will run 16 times to create the matches
number_of_fixtures.times do |n|
Fixture.creatse!(:tournament_leg_id => leg.id, :match_code => "#{leg.leg_code}-#{n+1}")
end
end
end
I can do nothing but speculate as to why my session variable is exceeding 4KB??
Is it possible that the tournament object I pass through the flash variable contains all the associations as well?
Here is the dump of my session once I get the error.
Hope this is enough info to help me out :)
Thanks
Session Dump
_csrf_token: "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
flash: {:tournament=>#<Tournament id: nil, tournament_name: "asd", tournament_description: "asdasd", game_id: 1, number_of_teams: 16, start_date: "2011-04-30 00:00:00", tournament_style: "single elimination", tournament_status: "Drafting", active: true, created_at: "2011-04-30 10:07:28", updated_at: "2011-04-30 10:07:28">}
player_id: 1
session_id: "4e5119cbaee3d5d09111f49cf47aa8fa"
About dependencies, it is possible. Also save an ActiveRecord instance in the session is not a recommended aproach. You should save only the id. If you need it in all your requests use a before filter to retrieve it.
You can read more why is a bad idea at: http://asciicasts.com/episodes/13-dangers-of-model-in-session
The generally accepted and recommended approach is to not use a redirect on error, but a direct render instead. The standard "controller formula" is this:
def create
#tournament = Tournament.new(params[:tournament])
if #tournament.save
redirect ...
else
render 'new' # which will have access to the errors on the #tournament object and any other instance variable you may define
end
end
class Tournament < ActiveRecord::Base
before_create :set_up_legs
end
On successful saving, you can drop all instance variables (thereby wiping the in-memory state) and redirect to another page. On failure (or exception) you keep the object in memory and render a view template instead (typically the 'new' or 'edit' form page). If you're using standard Rails validation and error handling, then the object will have an errors array that you can just display.
I'd also recommend you use ActiveRecord associations which automatically give you transactions. If you push all this into the model, e.g. a "set_up_legs" method or something, then you can use ActiveRecord error handling. This is part of the "skinny controller, fat model" paradigm.
in session_store.rb, uncomment the last line with :active_record_store
Now restart the server
I would convert the exception to string before assigning it to flash[:tournament] with 'to_s'.
I had the same error and it seems assigning an exception object to a session variabla like flash means it takes the whole stack trace with it into the session. Try it, worked for me.