Lifecycle of with_options in Rails. Why not working? - ruby-on-rails

Im with a very tricky problem with a validation code on my app.
I have a user and a talent. A User has_one Talent. And, depending on the steps of the application, I need to validate some cases.
Those are my classes.
class User < ApplicationRecord
has_one :talent, dependent: :destroy
has_one :client, dependent: :destroy
has_one :agency, dependent: :destroy
before_create :set_initial_step
after_create :build_type
def build_type
logger.debug("Im inside build_type ***************** with #{type}")
if type.nil?
t = Talent.create(user: self)
Rails.logger.debug("checking errors ******#{type}*********** with #{t.errors.full_messages}")
else
o = Object.const_get(type.capitalize).create(user: self)
Rails.logger.debug("Im inside build_type ****#{type}************* with #{o.errors.full_messages}")
end
end
class Talent < ApplicationRecord
with_options if: Proc.new { |t| t.user.approval_submission_step >= 2 } do |t|
byebug
t.validates :talent_type_id,
:rate,
presence: true
t.validates :rate, numericality: { greater_than_or_equal_to: 25 }
end
with_options if: Proc.new { |t| t.user.approval_submission_step >= 3 && t.talent_type.is_model } do |t|
t.validates :gender,
:ethnicity,
:height_feet,
:height_inches,
:weight,
:eye_color,
:hair_color,
presence: true
t.validates :men_pant_size_waist,
:men_pant_size_length,
:men_shirt,
:men_dress_shirt_size,
:men_dress_shirt_neck,
:men_dress_shirt_sleeve,
:men_jacket,
:men_shoe_size,
presence: true, if: Proc.new { |t| t.gender == 'male' }
t.validates :ethnicity,
:inclusion => {in: VALID_ETHNICITY_TYPES}
t.validates :womens_dress_size,
:women_shoe_size,
:women_shirt,
:womens_dress_size,
:women_pant,
:women_bra_cup,
:women_bra_size,
presence: true, if: Proc.new { |t| t.gender == 'female' }
end
First weird thing. The byebug in the middle of the with_options its called when the server starts or when I do a rails console.
When I create a new User and save it (using the callback after_create of the user)
t = Talent.create(user: self)
The byebug its not called.
What Im doing wrong? Why the with_options on Talents are being called on the class-loading process but not when creating a new one?

First weird thing. The byebug in the middle of the with_options its called when the server starts or when I do a rails console.
with_options is evaluated when the class is loaded, that's why byebug only stops the excecution on those moments. That's how it works.
I'm not really sure what you want to do with those byebug calls, but I think you want to add them inside the Proc's, that code is actually evaluated at the moment with the current instance.
with_options if: Proc.new { |t| byebug; t.user.approval_submission_step >= 2 } do |t|

Related

Rails NoMethodError: undefined method `password_digest=

I am new to rails and have seen all possible answers for my problem, as this is asked quite frequently by other developers, yet I'm unable to resolve it. Please have a look.
I am getting this error when I try to add a data from the console
User.create(name: "Michael Hartl", email: "mhartl#example.com", phone: "0123456789", password: "foobar", password_confirmation: "foobar")
ERROR SHOWN
undefined method `password_digest=' for #<User:0x0000000375c788>
Did you mean? password=
Controller
def create
#candidate = User.new(user_params)
if #candidate.save
flash[:notice] = "New Candidate Added Successfully"
redirect_to(users_path)
else
render('new')
end
end
private
def user_params
#Whitelisting for strng parameters
params.require(:user).permit(:name, :email, :password, :password_confirmation, :qualification, :college, :stream, :phone)
end
Migration:
class CreateUsers < ActiveRecord::Migration[5.1]
def change
create_table :users do |t|
t.string :name, null: false
t.boolean :admin_user, default: false
t.string :email, null: false
t.string :password_digest, null: false
t.string :qualification
t.string :college
t.string :stream
t.string :phone
t.timestamps
end
end
end
Model
require 'bcrypt'
class User < ApplicationRecord
include BCrypt
has_many :results, dependent: :destroy
has_many :exams, through: :results
accepts_nested_attributes_for :results
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
before_save { self.email = email.downcase }
has_secure_password
validates :email, presence: true
validates :email, presence: true, length: { maximum: 255 },format: { with: VALID_EMAIL_REGEX },uniqueness: { case_sensitive: false }
validates :password, presence: true, length: { minimum: 6 }
validates_confirmation_of :password
validates_presence_of :password_confirmation
validates :name, presence: true
validates :phone, numericality: {only_integer: true}, length: {is: 10 , message: "length should be 10"}
scope :visible, lambda { where(:visible => true) }
scope :invisible, lambda { where(:visible => false) }
scope :sorted, lambda { order("id ASC") }
scope :newest_first, lambda { order("created_at DESC") }
scope :search, lambda {|query| where(["name LIKE ?", "%#{query}%"]) }
end
Have you checked this https://github.com/plataformatec/devise/issues/1845
As per described in the issue, you are using ActiveModel's has_secure_password with Devise. You must not mix the two.
May be removing has_secure_password from User model will resolve your issue.
The code was completely fine,
few server restarts and don't really know how but restarting my code editor ( Visual Studio Code ) worked for me.
I am leaving the question as it is, as I did reach to this code after going through several stackoverflow threads.
NB: For newbies like me, please see how to check for errors while inserting into database from console, it helps a lot. Synatctical example is as:
rails c
user = User.new ( ... . . . . .. )
user.save
user.errors
Thankyou
This happened to me as well, and I found out that sometimes vscode doesn't actually kill the process running. You will need to kill rails from the terminal.
use ps aux | grep rails to find out what processes are running and use kill -9 [rails-pid] to stop the process.
On your next attempt, everything should work fine.
This answer helped me.

Why throwing abort in a nested association raises “Failed to destroy the record” exception?

I need to validate if any CameraVectors has been associated to any MonitoredPlace before I destroy a Camera.
Camera's Model
class Camera < ApplicationRecord
belongs_to :location
has_many :camera_vectors, inverse_of: :camera, dependent: :destroy
validates :description, :device_serial, :device_name,
:device_type, :device_api_url, :device_user, :device_password,
presence: true
accepts_nested_attributes_for :camera_vectors, allow_destroy: true
end
CameraVector's model
class CameraVector < ApplicationRecord
belongs_to :camera, inverse_of: :camera_vectors
belongs_to :monitored_place, optional: true
validates :description, presence: true
validates :position, numericality: { greater_than_or_equal_to: 0 }, presence: true
before_destroy :has_monitored_place?
private
def has_monitored_place?
if monitored_place.present?
errors.add(:base, "cannot delete")
throw :abort
end
end
end
MonitoredPlace's model
class MonitoredPlace < ApplicationRecord
belongs_to :location
belongs_to :place_type
has_many :camera_vectors
validates :place_name, presence: true
validates :place_type_id, uniqueness: { scope: :location_id }, presence: true
scope :enabled, -> { where.not(enabled_on: nil).where(disabled_on: nil) }
end
Because of the accepts_nested_attributes_for whenever I try to update or destroy a camera this nested fields are sent as params
"camera_vectors_attributes"=>{"0"=>{"description"=>"A", "position"=>"1", "_destroy"=>"1", "id"=>"47"}}
I thought If I wrote a callback before_destroy in the model CameraVector I could validate it, but if the validation occurs it raises ActiveRecord::RecordNotDestroyed in the controller.
if #camera.destroy(camera_params)
redirect_to(action: :index, notice: t(".success"))
else
render :index
end
as you can read in the api documentation
ActiveRecord::RecordNotDestroyed
Raised by ActiveRecord::Base#destroy! when a call to #destroy would return false.
It is result of
before_destroy :has_monitored_place?
that calls a method and returns false.
def has_monitored_place?
if monitored_place.present?
errors.add(:base, "cannot delete")
throw :abort
end
end
to change this behavior implement a logic similar to the one described in the api
begin
complex_operation_that_internally_calls_destroy!
rescue ActiveRecord::RecordNotDestroyed => invalid
puts invalid.record.errors
end
or read
How do I 'validate' on destroy in rails

Rails validation with if

I am having trouble getting my validation working properly. I am trying to check if the user has checked :noship then :weight must equal 0.
With the code below I get an "Undefined local variable noship" error
snippet from models/package.rb
class Package < ActiveRecord::Base
belongs_to :campaign
validates_presence_of :weight, :campaign
validates :weight, numericality: { equal_to: 0 }, :if => :noship_test?
def noship_test?
noship == true
end
rails_admin do
object_label_method do
:custom_name
end
end
The :noship and :weight values are working and being saved correctly to my database
Since noship belongs to campaign model, you'll need to do something like:
def noship_test?
campaign && campaign.noship
end
However having such a method seems redundant, just pass a lambda to if key:
validates :weight, numericality: { equal_to: 0 }, :if => ->(r) { r.campaign && r.campaign.noship }

NoMethodError - undefined method `by_email' for ActiveModel::MassAssignmentSecurity::BlackList:Class:

I have an email_address object that I am trying to check to see if it is on the blacklist for that particular domain. I'm calling it like this:
elsif #email.blacklisted?(#domain.id)
do something ...
end
I am getting the error:
NoMethodError - undefined method `by_email' for ActiveModel::MassAssignmentSecurity::BlackList:Class:
I have also tried doing a .find_all_by_id instead of using the blacklist scopes I created. Same error though. This is driving me crazy, any ideas would be amazing!
EmailAddress Class
class EmailAddress < ActiveRecord::Base
attr_accessible :email, :global_blacklist
has_many :transactions
has_many :black_lists
has_many :opt_outs
validates :email, :presence => true,
:uniqueness => true
validates :global_blacklist, :acceptance => true
def blacklisted?(domain_id)
black_lists = BlackList.by_email(self.id).by_domain(domain_id)
black_lists.count > 0
end
end
BlackList Class
class BlackList < ActiveRecord::Base
attr_accessible :domain_id, :email_address_id, :date_added
belongs_to :domain
belongs_to :email_address
validates :domain_id, :presence => true
validates :email_address_id, :presence => true
validates :date_added, :presence => true
GLOBAL_BLACK_LIST_THRESHOLD = 2
scope :by_domain, ->(domain_id) { where('domain_id = ?', domain_id) }
scope :by_email, ->(email_id) { where('email_address_id = ?', email_id) }
end
Just in case anyone else has this problem...
The class name BlackList (capitol L) seems to be an ActiveModel Class Name.
I changed the class name to Blacklist (one word instead of two) and the problem went away.

ActiveModel::MassAssignmentSecurity::Error even when using accepts_nested_attributes_for

My complete error message is:
ActiveModel::MassAssignmentSecurity::Error in
WorkoutsController#create Can't mass-assign protected attributes:
workout_entry
The params that I am sending looks like:
{"workout"=>{"unit"=>"kg", "name"=>"2013-02-20T21:26:19", "note"=>nil, "workout_entry"=> [{"workout_entry_number"=>"1", "exercise_id"=>2, "entry_detail"=>[{"set_number"=>"1", "weight"=>"32", "reps"=>"43"}]}]}}
I have a workout that has many workout entries and each workout entries can have many entry details. The note is optional.
workout.rb
class Workout < ActiveRecord::Base
has_many :workout_entries, dependent: :destroy
attr_accessible :id, :name, :note, :unit, :workout_entries_attributes
belongs_to :user
accepts_nested_attributes_for :workout_entries
validates_presence_of :name
validates_presence_of :unit, :inclusion => %w(kg lb)
validates_associated :workout_entries
default_scope order("created_at DESC")
end
workout_entry.rb
class WorkoutEntry < ActiveRecord::Base
belongs_to :workout
belongs_to :exercise
has_many :entry_details, dependent: :destroy
attr_accessible :workout_id, :exercise_id, :workout_entry_number, :entry_details_attributes
accepts_nested_attributes_for :entry_details
validates :exercise_id, presence: true, numericality: {only_integer: true}, :inclusion => { :in => 1..790 }
validates :workout_id, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1}
validates :workout_entry_number, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 1}
end
workouts_controller.rb
class WorkoutsController < ApplicationController
respond_to :json
before_filter :authenticate_user!
def index
respond_with(current_user.workouts)
end
def show
respond_with(current_user.workouts.find(params[:id]))
end
def create
respond_with(current_user.workouts.create(params[:workout]))
end
def update
#workout = current_user.workouts.find(params[:id])
if #workout.update_attributes(params[:workout])
render json: #workout, status: :ok
else
render json: #workout.errors, status: :unprocessable_entity
end
end
def destroy
respond_with(current_user.workouts.destroy(params[:id]))
end
end
I tried switching the ordering of attr_accessible and accepts_nested_attributes_for within the workout.rb, but it does not work.
I even tried to set
config.active_record.whitelist_attributes = true
but creating was still prevented.
accepts_nested_attributes_for does not add any attributes to the whitelist. Whatever keys your trying to pass to update_attributes have to be listed in attr_accessible, in your case you need to add workout_entry to attr_accessible.
It does look like you have an error in the form, if your using fields_for then it should be using the key workout_entries_attributes, which you have accessible.
Try to add workout_entry_ids in attr accessible in your workout model.
I decided to not use accepts_nested_attributes_for in the workout and workout_entry models because it wasn't working for me. I also updated the format of my json that is sent. Details are in the link below
link

Resources