0
class Client < ApplicationRecord
  has_many :projects
  validates :name, presence: true

  validates :phone,
    presence: {
      message: "Phone or Email can not be blank",
      if: Proc.new { |a| a.email.blank? }
    },
    length: {
      minimum: 10,
      unless: Proc.new { |a| a.phone.blank? }
    }

  validates :email,
    uniqueness: {
      unless: Proc.new { |a| a.email.blank? }
    },
    presence: {
      message: "Phone/Email can't both be blank",
      if: Proc.new { |a| a.phone.blank? }
    },
    format: {
      with: URI::MailTo::EMAIL_REGEXP,
      unless: Proc.new { |a| a.email.blank? }
    }

    def phone_blank?
      Proc.new { |a| a.phone.blank? }
    end
end

How do I create a method to replace with all the Proc? I just learned about Proc and I'm not too familiar with that yet. I tried to use :phone_blank to replace all the proc after if:/unless:, but it failed to work. Can someone tell me how to make the phone_blank? method work to replace all the proc embeded in the code? thanks~

edited: I forgot to mention I'm using rails_admin for the admin interface. If I call methods in if:/unless:, the admin panel will show Model 'Client' could not be found then the model would disappear from the admin panel. I'm not sure it's a rails_admin thing or that's how Rails 5 behaves. I'm quite new to RoR and still quite confuse with all different versions of Rails....

MorboRe'
  • 151
  • 10

3 Answers3

0

For using method there is no need in Proc wrapper.

e.g.

class Client < ApplicationRecord
  has_many :projects
  validates :name, presence: true

  validates :phone,
    presence: {
      message: "Phone or Email can not be blank",
      if: email_blank?
    },
    length: {
      minimum: 10,
      unless: phone_blank?
    }

  validates :email,
    uniqueness: {
      unless: email_blank?
    },
    presence: {
      message: "Phone/Email can't both be blank",
      if: phone_blank?
    },
    format: {
      with: URI::MailTo::EMAIL_REGEXP,
      unless: email_blank?
    }

  def phone_blank?
    phone.blank?
  end

  def email_blank?
    email.blank?
  end
end

Also you can simply specify this condition in validation directly without method or Proc as a string.

e.g.

class Client < ApplicationRecord
  has_many :projects
  validates :name, presence: true

  validates :phone,
    presence: {
      message: "Phone or Email can not be blank",
      if: 'email.blank?'
    },
    length: {
      minimum: 10,
      if: 'phone.present?'
    }

  validates :email,
    uniqueness: {
      if: 'email.present?'
    },
    presence: {
      message: "Phone/Email can't both be blank",
      if: 'phone.blank?'
    },
    format: {
      with: URI::MailTo::EMAIL_REGEXP,
      if: 'email.present?'
    }
end

sfate
  • 155
  • 10
  • Are you sure about the `if: 'email.blank?'` things? – mu is too short Aug 21 '18 at 21:09
  • 1
    rails ~5 doesn't take string for if: and unless: I think. – MorboRe' Aug 21 '18 at 21:13
  • I've tried this way but it doens't work in Rails 5.2.1 I think. I'm using rails_admin for the admin interface. The client panel would dissapear when I declare a method then call the method in if:/unless:. I don't know if it's a rails_admin thing or that's how Rails 5 works – MorboRe' Aug 21 '18 at 21:18
  • Yup, correct, rails >5 doesn't accept strings for conditional validations. – sfate Aug 21 '18 at 21:23
0

You could write a class method that returns a lambda, something like:

def self.blank_field?(field)
  ->(m) { m.send(field).blank? }
end

and then say things like:

validates :phone,
  presence: {
    message: "Phone or Email can not be blank",
    if: blank_field?(:email)
  },
  length: {
    minimum: 10,
    unless: blank_field?(:phone)
  }

Note that we use blank_field? instead of blank? since blank? is already taken and we don't want to override it. And since this is an "internal" method, we don't have to worry about public_send versus send.

mu is too short
  • 426,620
  • 70
  • 833
  • 800
  • I'd suggest to replace `send` with `[]` to read attribute on a model. e.g. `m[field].blank?` – sfate Aug 21 '18 at 21:25
  • I forgot to mention I'm using rails_admin. I'm starting to suspect that because of rails_admin, the solutions you mention cease to work. I really don't know. too noob.... thanks for the solution though. I will go dig more about lambda – MorboRe' Aug 21 '18 at 21:25
  • sfate, my code would throw an error when I replace send with [] – MorboRe' Aug 21 '18 at 21:28
  • @sfate But using `#[]` will bypass an accessor method (if you have one) and that can lead to unexpected behavior. – mu is too short Aug 21 '18 at 22:12
  • @MorboRe' Are you getting a NameError in the console or the logs? Have you defined `blank_field?` before you try to use it? – mu is too short Aug 21 '18 at 22:15
  • @MorboRe' agree, but it's not safe to use just `#send`, at least it should be `public_send` and probably check on `#respond_to?` as well. – sfate Aug 21 '18 at 22:20
  • @sfate Public/private is irrelevant in this context so there's no need to bother with `public_send`. There's no much point to checking `#respond_to?` either, your test suite should pick up any typos. – mu is too short Aug 21 '18 at 23:44
  • @mu is too short, got an error from rails_admin, I think all the problems are caused by rails_admin not accepting certain format of code. Maybe I should just drop rails_admin and get better with Rails before move on the any plug-in of it. – MorboRe' Aug 22 '18 at 01:37
  • But is there anything in the error logs? Have you tried to run the code from the Rails console? – mu is too short Aug 22 '18 at 04:43
0

Not a direct answer, but an alternative approach in DRY-ing things is to make use of with_options:

with_options if: -> { email.blank? } do
  validates :phone, presence: { message: "Phone or Email can not be blank" }
end

with_options if: -> { phone.blank? } do
  validates :email, presence: { message: "Phone/Email can't both be blank" }
end

with_options if: -> { email.present? } do
  validates :phone, length: { minimum: 10 }
  validates :email, uniqueness: true, format: { with: URI::MailTo::EMAIL_REGEXP }
end

This is especially useful when the validations have conditions depending on different... say, categories (if you have a category column), and you can just simply group these validations up with_options

Trivia:

You can think of -> { ... } just like Proc.new { ... } which you were already familiar with (though accurately speaking it's a lambda ... which is like a special type of Proc. If you're interested further, see these SO posts: HERE and HERE

Jay-Ar Polidario
  • 6,463
  • 14
  • 28