Rails: after_create callback triggered only on production - ruby-on-rails

Anyone know why an after_create callback is triggered only on production environment ?
class Schedule < ActiveRecord::Base
belongs_to :account
validates :schedule_type, presence: true # default = 'appointment'
after_create :send_notification, if: :devices_and_appointment?
def devices_and_appointment?
account_has_devices? && schedule_type.appointment?
end
def account_has_devices?
!account.devices.blank?
end
def appointment?
schedule_type.eql?('appointment')
end
end
On production, the callback is triggered, even if schedule_type is not appointment. I check the logs and the controller receive the params as expected (schedule_type != appointment)
For a strange reason, when after_create is invoked, the schedule_type is the default.
We check this on development and even in staging env and we cannot reproduce the bug.
The differences between staging and production is the DB sharding (Octopus) but we only have this activated for reads, in some other controllers, not even in the ScheduleController
We end up moving the callback method to a service, calling the function after the .save and that fix the problem, but I still want to know why this happen.
Controller:
def create
#schedule = Schedule.new(schedule_params)
if #schedule.save
render json: #schedule, status: 201
else
render json: { errors: #schedule.errors }, status: 422
end
end
def schedule_params
params
.require(schedule)
.permit(:account_id, :cause, :ends_at, :schedule_type, :starts_at)
end
Request:
Parameters: {"schedule"=>{"cause"=>"cause random", "starts_at"=>"2018-09-17T21:00:00.000-05:00", "ends_at"=>"2018-09-17T21:30:00.000-05:00", "schedule_type"=>"blocked", "account_id"=>"123"}}

Related

Rails Saving a Status Value, (compared using ==)

I'm trying to create a mailer and allow a status to be changed to pending upon creation. So I have the following in my controller:
class Api::UserTrainingResourcesController < Api::BaseController
respond_to :html, :json
def create
#user_training_resource = UserTrainingResource::Create.call(user_training_resource_params)
respond_with(#user_training_resource)
end
def destroy
#user_training_resource = UserTrainingResource.find_by(params[:id])
#user_training_resource.destroy
end
private
def user_training_resource_params
params.require(:user_training_resources).permit(:training_resources_id, :status).merge(spud_user_id: current_user_id)
end
end
Then in my Operations I have the following create:
class UserTrainingResource
class Create < Operation
def call(params)
params[:status] = :pending
training_resource = UserTrainingResource.new(params)
UserTrainingResourceMailer.requested(training_resource).deliver_later if training_resource.save
training_resource
end
end
end
My model has:
class UserTrainingResource < ApplicationRecord
belongs_to :user
belongs_to :training_resource
enum status: [:pending, :confirmed, :rejected], _prefix: :status
scope :employee, -> { where.not(status: ['rejected']) }
scope :hr, -> { where(status: SameScope::HR) }
module SameScope
HR = ['pending', 'confirmed', 'rejected'].freeze
end
def view_user_training_resource_request?(user)
return true if user.human_resources && SameScope::HR.include?(status)
false
end
def change_status?(user, status)
return true if user.human_resources && SameScope::HR.include?(status)
false
end
end
Then in my test I have:
require 'rails_helper'
RSpec.describe UserTrainingResource::Create do
let(:params) { attributes_for(:user_training_resource).merge(user_id: create(:user).id) }
describe '#call' do
it 'queues a mailer' do
ut = UserTrainingResource::Create.call(params)
expect(UserTrainingResourceMailer).to send_mail(:requested)
expect(ut.status).to eq('pending')
end
end
end
So this ends up with a Failure/Error: expect(ut.status).to eq('pending') Expect: "pending", got" nil. (compared using ==)
I thought the issue was that pending was not saving to the db but that's because I had status present as a string not an integer when using enum in the model. But it's still getting nil.
Edit:
After noticing that I had status using string I went ahead and added in a migration file to change it to integer. Test failed with the same issue. Tried to rollback my test environment, failed. Did a rake db:reset. Re-added in data in both development and test. Still getting the issue of essentially:
Failure/Error: expect(ut.status).to eq('pending')
expected: "pending"
got: nil
(compared using ==)

Rails 5: attr_accessor throwing NoMethodError (undefined method `keys' for nil:NilClass):

I have 2 non database attributes in my model. If one of them has a value, I need to return the other one in the json response:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def attributes
if !self.track.nil?
super.merge('max_speed_on_track' => self.max_speed_on_track)
end
end
end
The problem is that the line 'if !self.track.nil?' throws an error when the controller tries to return the json
Perhaps there is a better way as I read that using attr_accessor is a code smell.
What I am trying to do is if the user passes me a track value as a query parameter, then I pass that value to the model and it uses it to calculate the max_speed_on_track, and return that value.
Obviously if no track is provided by the user then I don't want to return max_speed_on_track in the json.
The controller method is very basic for now (I still need to add the code that checks for the track param). The code throws the error on the save line.
def create
#car = Car.new(car_params)
if #car.save
render json: #car, status: :created
else
render json: #car.errors, status: :unprocessable_entity
end
end
Try this out:
class Car < ApplicationRecord
attr_accessor :max_speed_on_track
attr_accessor :track
def as_json(options = {})
if track.present?
options.merge!(include: [:max_speed_on_track])
end
super(options)
end
end
Since Rails uses the attributes method, and you're only needing this for json output, you can override the as_json method just like in this article. This will allow you to include your max_speed_on_track method in your json output when the track is present (not nil).

rails error added to object but not raised

I'm doing an update of a form. I can't add my validation in my model for x reason, so I'm adding an error in my projects_controller in the method update. When I update it should raise the error and render :edit but it doesn't. Here is my method
def update
#project = Project.find(params[:id])
#stuff to update
#add error if no legal_media checked, unless if creative upload its own conditions
unless has_media?(#project.legal_option.authorized_format)
#project.legal_option.authorized_format.errors[:base] << "error message"
end
if #project.update_attributes(project_params)
redirect_to brief_path(#project.order.brief)
else
render :edit
end
end
the method has_media? returns false dans when I type #project.legal_option.authorized_format.errors[:base]I have my error message ["error message"].
But when I type #project.legal_option.authorized_format.valid?, it returns true
Any idea how I could make my method raise this error?
Thank you!
UPDATE trying to do the validation in the model :
Since the beginning I want to check that if my column custom_document in legal_option isn't nil (therefore the user uploaded it in the update method of the projects_controller), then, check if there is at least one media in legal_media.
Here are my models :
class LegalOption < ActiveRecord::Base
belongs_to :project
has_one :authorized_format, class_name: "LegalMedia", foreign_key: "legal_option_id"
accepts_nested_attributes_for :authorized_format
has_attached_file :custom_document
validates_attachment :custom_document, content_type: { content_type: "application/pdf" }
end
class LegalMedia < ActiveRecord::Base
belongs_to :legal_option
def self.formats
{all_media: "Tous Media", internet: "Internet", paper: "Presse papier", object: "Objets", television: "TV", radio: "Radio", cinema: "Cinéma", poster_campaign: "Affiches", :press_relation => "Relations Presse", :plv => "Publicité sur lieux de vente", :event => 'Evènementiel'}
end
end
When I did the validation in the beginning with a validate :has_media? My LegalOption.LegalMedia because legal_option_id is nil in legal_media
in the unless block, put the line:
render :edit and return
like:
unless has_media?(#project.legal_option.authorized_format)
#project.legal_option.authorized_format.errors[:base] << "error message"
render :edit and return
end
You should add a validation to the model in order for the valid? to do what you are looking for it to do.
If you look at the docs here, you'll see that valid? just runs all the validations. It doesn't check for any errors that you manually add to the object.
Rails convention dictates that validations shouldn't be implemented in the controller but rather in the model. More specifically, update_attributes just runs valid? after assigning the attributes, which itself just runs validations defined on the model. Any errors already on the model are cleared out beforehand.
If you re-write this as a custom validation on the model, update_attributes should behave as you expect:
class Project < ActiveRecord::Base
validate :legal_option_has_media
private
def legal_option_has_media
unless has_media? legal_option.authorized_format
errors.add :base, "error message"
end
end
end

Rails render json validation issue

Two issues here. First is that I need to access a model's id before all of its attributes are defined. Meaning that this:
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data
end
throws an error unless I removed the second line, which is not a good thing to do. My second issue is that I don't want to render json until a model has both attributes. This is my controller:
def create
#search = Search.create( name: (params[:name]) )
Resque.enqueue(InstagramWorker, #search.id)
respond_to do |format|
if #search.save
format.json { render json: #search }
format.html { redirect_to root_path }
else
format.html { redirect_to root_path }
end
end
end
Should I write some logic in the model to check for name && color_data before saving? And is there a workaround for accessing an id without breaking validations?
You probably can use conditional validations, like
class Search < ActiveRecord::Base
validates_presence_of :name
validates_presence_of :color_data, if: :some_condition?
private
def some_condition?
# condition logic here
end
end
You can't do this.
By calling Resque.enqueue(InstagramWorker, #search.id) you're telling resque to do something, but not as part of this request. So this could complete now, it could complete in 2 hours from now.
If you need to ensure that this completes before the request has finished, take it out of Resque.
What you could do is only validate the color_data on update, rather than create. Presumably your resqueue job calls #search.save. So by adding
validates :color_data, presence: true, on: :update
But this wouldn't stop the json being rendered, you can't get past the fact that this is not part of the request without taking it out of resqueue.

Ruby on Rails Controller action is a private method

I am getting
private method `new' called for Reminder:Class
The Application trace is
app/controllers/reminders_controller.rb:27:in `new'
The new action is as follows
def new
#reminder = #current_user.reminders.build()
#title = "New Reminder"
respond_to do |format|
format.html # new.html.erb
format.json { render json: #reminder }
end
end
The Reminder Model is has follows
class Reminder < ActiveRecord::Base
belongs_to :user
belongs_to :assignment
attr_accessible :datetime, :sent_at, :status, :send_time
STATUSES = ["Not Sent", "Sending", "Sent", "Canceled"]
validates_presence_of :sent_at, :status, :user_id, :assignment_id
before_save :round_tine
def round_time
self.send_time = Time.at(t.to_i/(15*60)*(15*60))
end
end
I don't know how the method would be private. Thanks for the help in advance!
UPDATE: Added a method to the model. Error still occurs.
put mailer class name as ReminderMailer not just Reminder. That's the problem rails is not able to distinguish between two classes and it is identifying the new method for mailer class which has name Reminder and showing the error.
You probably have the private declaration somewhere above your new definition. Post the entirety of your reminders_controller or just remove that offending line.

Resources