{"action":"created","issue":{"url":"https://api.github.com/repos/rails/rails/issues/23645","repository_url":"https://api.github.com/repos/rails/rails","labels_url":"https://api.github.com/repos/rails/rails/issues/23645/labels{/name}","comments_url":"https://api.github.com/repos/rails/rails/issues/23645/comments","events_url":"https://api.github.com/repos/rails/rails/issues/23645/events","html_url":"https://github.com/rails/rails/issues/23645","id":133346523,"number":23645,"title":"Presence validation of virtual attribute not firing appropriately","user":{"login":"derekprior","id":152152,"avatar_url":"https://avatars.githubusercontent.com/u/152152?v=3","gravatar_id":"","url":"https://api.github.com/users/derekprior","html_url":"https://github.com/derekprior","followers_url":"https://api.github.com/users/derekprior/followers","following_url":"https://api.github.com/users/derekprior/following{/other_user}","gists_url":"https://api.github.com/users/derekprior/gists{/gist_id}","starred_url":"https://api.github.com/users/derekprior/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/derekprior/subscriptions","organizations_url":"https://api.github.com/users/derekprior/orgs","repos_url":"https://api.github.com/users/derekprior/repos","events_url":"https://api.github.com/users/derekprior/events{/privacy}","received_events_url":"https://api.github.com/users/derekprior/received_events","type":"User","site_admin":false},"labels":[{"url":"https://api.github.com/repos/rails/rails/labels/activerecord","name":"activerecord","color":"0b02e1"},{"url":"https://api.github.com/repos/rails/rails/labels/regression","name":"regression","color":"e10c02"}],"state":"open","locked":false,"assignee":{"login":"eileencodes","id":1080678,"avatar_url":"https://avatars.githubusercontent.com/u/1080678?v=3","gravatar_id":"","url":"https://api.github.com/users/eileencodes","html_url":"https://github.com/eileencodes","followers_url":"https://api.github.com/users/eileencodes/followers","following_url":"https://api.github.com/users/eileencodes/following{/other_user}","gists_url":"https://api.github.com/users/eileencodes/gists{/gist_id}","starred_url":"https://api.github.com/users/eileencodes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eileencodes/subscriptions","organizations_url":"https://api.github.com/users/eileencodes/orgs","repos_url":"https://api.github.com/users/eileencodes/repos","events_url":"https://api.github.com/users/eileencodes/events{/privacy}","received_events_url":"https://api.github.com/users/eileencodes/received_events","type":"User","site_admin":false},"milestone":{"url":"https://api.github.com/repos/rails/rails/milestones/47","html_url":"https://github.com/rails/rails/milestones/5.0.0","labels_url":"https://api.github.com/repos/rails/rails/milestones/47/labels","id":1467326,"number":47,"title":"5.0.0","description":"","creator":{"login":"rafaelfranca","id":47848,"avatar_url":"https://avatars.githubusercontent.com/u/47848?v=3","gravatar_id":"","url":"https://api.github.com/users/rafaelfranca","html_url":"https://github.com/rafaelfranca","followers_url":"https://api.github.com/users/rafaelfranca/followers","following_url":"https://api.github.com/users/rafaelfranca/following{/other_user}","gists_url":"https://api.github.com/users/rafaelfranca/gists{/gist_id}","starred_url":"https://api.github.com/users/rafaelfranca/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rafaelfranca/subscriptions","organizations_url":"https://api.github.com/users/rafaelfranca/orgs","repos_url":"https://api.github.com/users/rafaelfranca/repos","events_url":"https://api.github.com/users/rafaelfranca/events{/privacy}","received_events_url":"https://api.github.com/users/rafaelfranca/received_events","type":"User","site_admin":false},"open_issues":44,"closed_issues":321,"state":"open","created_at":"2015-12-18T21:17:59Z","updated_at":"2016-02-13T14:45:04Z","due_on":null,"closed_at":null},"comments":4,"created_at":"2016-02-12T21:23:49Z","updated_at":"2016-02-13T14:57:43Z","closed_at":null,"body":"In [Clearance](https://github.com/thoughtbot.clearance) we use a conditional validation on the virtual `password` attribute to determine whether the password field needs to be validated. We only need to validate it when the underlying database row's `encrypted_password` is not set or if the user is changing the password.\r\n\r\nI found that when testing against rails 5, the conditional validation does not behave as expected. The method that guards the validation returns as it did under 4.2, but calls to `valid?` return `true` when they should return `false`. Interestingly, the call to `save` inside the `valid?` conditional *does* fire the validation appropriately.\r\n\r\nIt seems `valid?` is short circuiting, maybe because no active-record level changes have been detected? Yet `save` does not short circuit?\r\n\r\nHere's a test case:\r\n\r\n```ruby\r\nbegin\r\n  require 'bundler/inline'\r\nrescue LoadError => e\r\n  $stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'\r\n  raise e\r\nend\r\n\r\ngemfile(true) do\r\n  source 'https://rubygems.org'\r\n  gem 'rails', github: 'rails/rails'\r\n  gem 'arel', github: 'rails/arel'\r\n  gem 'rack', github: 'rack/rack'\r\n  gem 'sprockets', github: 'rails/sprockets'\r\n  gem 'sprockets-rails', github: 'rails/sprockets-rails'\r\n  gem 'sass-rails', github: 'rails/sass-rails'\r\n\r\n  # gem 'activerecord', '~> 4.2.0'\r\n  gem 'sqlite3'\r\n  gem 'pry'\r\nend\r\n\r\nrequire 'active_record'\r\nrequire 'minitest/autorun'\r\nrequire 'logger'\r\n\r\n# This connection will do for database-independent bug reports.\r\nActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')\r\nActiveRecord::Base.logger = Logger.new(STDOUT)\r\n\r\nActiveRecord::Schema.define do\r\n  create_table :users, force: true  do |t|\r\n    t.string :email, null: false\r\n    t.string :encrypted_password, null: false\r\n    t.timestamp :password_updated_at\r\n  end\r\nend\r\n\r\nclass User < ActiveRecord::Base\r\n  attr_accessor :password_changing\r\n  attr_reader :password\r\n  validates :email, presence: true\r\n  validates :password, presence: true, unless: :skip_password_validation?\r\n\r\n  def update_password(new_password)\r\n    self.password_changing = true\r\n    self.password = new_password\r\n\r\n    puts \"skip_password_validation?    #{skip_password_validation?}\"\r\n    puts \"password.blank?              #{password.blank?}\"\r\n    puts \"valid?                       #{valid?}\"\r\n\r\n    if valid?\r\n      self.password_updated_at = Time.now\r\n      save # Interestingly, this does fail with password can't be blank...\r\n    end\r\n  end\r\n\r\n  private\r\n\r\n  def password=(new_password)\r\n    @password = new_password\r\n\r\n    if new_password.present?\r\n      self.encrypted_password = new_password.reverse\r\n    end\r\n  end\r\n\r\n  def skip_password_validation?\r\n    encrypted_password.present? && !password_changing\r\n  end\r\nend\r\n\r\nclass BugTest < Minitest::Test\r\n  def test_validation\r\n    user = User.create!(email: 'foo@bar.com', encrypted_password: 'olleh')\r\n\r\n    user.update_password(\"\")\r\n\r\n    assert user.password_updated_at == nil\r\n  end\r\nend\r\n```\r\n"},"comment":{"url":"https://api.github.com/repos/rails/rails/issues/comments/183682604","html_url":"https://github.com/rails/rails/issues/23645#issuecomment-183682604","issue_url":"https://api.github.com/repos/rails/rails/issues/23645","id":183682604,"user":{"login":"eileencodes","id":1080678,"avatar_url":"https://avatars.githubusercontent.com/u/1080678?v=3","gravatar_id":"","url":"https://api.github.com/users/eileencodes","html_url":"https://github.com/eileencodes","followers_url":"https://api.github.com/users/eileencodes/followers","following_url":"https://api.github.com/users/eileencodes/following{/other_user}","gists_url":"https://api.github.com/users/eileencodes/gists{/gist_id}","starred_url":"https://api.github.com/users/eileencodes/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/eileencodes/subscriptions","organizations_url":"https://api.github.com/users/eileencodes/orgs","repos_url":"https://api.github.com/users/eileencodes/repos","events_url":"https://api.github.com/users/eileencodes/events{/privacy}","received_events_url":"https://api.github.com/users/eileencodes/received_events","type":"User","site_admin":false},"created_at":"2016-02-13T14:57:43Z","updated_at":"2016-02-13T14:57:43Z","body":"Nevermind, I need more :coffee: :flushed: "}}