Skip to content
This repository

Added absence validator for active model. #7155

Closed
wants to merge 14 commits into from
robotex82

Please accept my pull request for an absence validator.

See following discussion: https://groups.google.com/forum/#!topic/rubyonrails-core/KS_Olcnlw8w

I included passing tests and docs (Copied and adapted from the presence validator).

Thanks!

Saludos,

Roberto

Robert Pankowecki
paneq commented July 25, 2012

+1

Evgeniy Dolzhenko

+1 Can confirm that needed this in almost every project with relatively complex models

robotex82

I use it on trees, where i need attributes to be empty on roots. But I think, there should be better examples.

Anyone?

Godfrey Chan
Collaborator

With the strict option set to true, this could be used to verify the integrity of your models... so I'm +1 on this.

Kendall Buchanan

+1 – Require first and last names for users older than 13, but prevent update for younger than 13.

Francesco Rodríguez frodsan commented on the diff October 26, 2012
activemodel/test/cases/validations/absence_validation_test.rb
((37 lines not shown))
  37
+  end
  38
+
  39
+  def test_accepts_array_arguments
  40
+    Topic.validates_absence_of %w(title content)
  41
+    t = Topic.new
  42
+    
  43
+    t.title = "foo"
  44
+    t.content = "bar"  
  45
+    
  46
+    assert t.invalid?
  47
+    assert_equal ["must be blank"], t.errors[:title]
  48
+    assert_equal ["must be blank"], t.errors[:content]
  49
+  end
  50
+
  51
+  def test_validates_acceptance_of_with_custom_error_using_quotes
  52
+    Person.validates_absence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
1
Francesco Rodríguez
frodsan added a note October 26, 2012

Please use 1.9 hash syntax. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Francesco Rodríguez

I'm :+1: on this.

activemodel/lib/active_model/validations/absence.rb
... ...
@@ -0,0 +1,34 @@
  1
+require 'active_support/core_ext/object/blank'
  2
+
  3
+module ActiveModel
  4
+
  5
+  # == Active Model Absence Validator
1
Francesco Rodríguez
frodsan added a note October 26, 2012

Could you move this title to class AbsenceValidator?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Francesco Rodríguez

@robotex82 Could you add a CHANGELOG entry please? Thanks!

activemodel/test/cases/validations/absence_validation_test.rb
((5 lines not shown))
  5
+require 'models/person'
  6
+require 'models/custom_reader'
  7
+
  8
+class AbsenceValidationTest < ActiveModel::TestCase
  9
+
  10
+  teardown do
  11
+    Topic.reset_callbacks(:validate)
  12
+    Person.reset_callbacks(:validate)
  13
+    CustomReader.reset_callbacks(:validate)
  14
+  end
  15
+
  16
+  def test_validate_absences
  17
+    Topic.validates_absence_of(:title, :content)
  18
+
  19
+    t = Topic.new
  20
+    
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva carlosantoniodasilva commented on the diff October 27, 2012
activemodel/test/cases/validations/absence_validation_test.rb
... ...
@@ -0,0 +1,90 @@
  1
+# encoding: utf-8
  2
+require 'cases/helper'
  3
+
  4
+require 'models/topic'
  5
+require 'models/person'
  6
+require 'models/custom_reader'
  7
+
  8
+class AbsenceValidationTest < ActiveModel::TestCase
  9
+
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva carlosantoniodasilva commented on the diff October 27, 2012
activemodel/test/cases/validations/absence_validation_test.rb
((27 lines not shown))
  27
+
  28
+    t.title = ""
  29
+    t.content  = "something"
  30
+
  31
+    assert t.invalid?
  32
+    assert_equal ["must be blank"], t.errors[:content]
  33
+
  34
+    t.content = ""
  35
+
  36
+    assert t.valid?
  37
+  end
  38
+
  39
+  def test_accepts_array_arguments
  40
+    Topic.validates_absence_of %w(title content)
  41
+    t = Topic.new
  42
+    
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva carlosantoniodasilva commented on the diff October 27, 2012
activemodel/test/cases/validations/absence_validation_test.rb
((39 lines not shown))
  39
+  def test_accepts_array_arguments
  40
+    Topic.validates_absence_of %w(title content)
  41
+    t = Topic.new
  42
+    
  43
+    t.title = "foo"
  44
+    t.content = "bar"  
  45
+    
  46
+    assert t.invalid?
  47
+    assert_equal ["must be blank"], t.errors[:title]
  48
+    assert_equal ["must be blank"], t.errors[:content]
  49
+  end
  50
+
  51
+  def test_validates_acceptance_of_with_custom_error_using_quotes
  52
+    Person.validates_absence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
  53
+    p = Person.new
  54
+    
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/test/cases/validations/absence_validation_test.rb
((51 lines not shown))
  51
+  def test_validates_acceptance_of_with_custom_error_using_quotes
  52
+    Person.validates_absence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
  53
+    p = Person.new
  54
+    
  55
+    p.karma = "good"
  56
+    
  57
+    assert p.invalid?
  58
+    assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
  59
+  end
  60
+
  61
+  def test_validates_absence_of_for_ruby_class
  62
+    Person.validates_absence_of :karma
  63
+
  64
+    p = Person.new
  65
+    
  66
+    p.karma = "good"
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/test/cases/validations/absence_validation_test.rb
((54 lines not shown))
  54
+    
  55
+    p.karma = "good"
  56
+    
  57
+    assert p.invalid?
  58
+    assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
  59
+  end
  60
+
  61
+  def test_validates_absence_of_for_ruby_class
  62
+    Person.validates_absence_of :karma
  63
+
  64
+    p = Person.new
  65
+    
  66
+    p.karma = "good"
  67
+    
  68
+    assert p.invalid?
  69
+
1

Remove blank line.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/test/cases/validations/absence_validation_test.rb
((70 lines not shown))
  70
+    assert_equal ["must be blank"], p.errors[:karma]
  71
+
  72
+    p.karma = nil
  73
+    assert p.valid?
  74
+  end
  75
+
  76
+  def test_validates_absence_of_for_ruby_class_with_custom_reader
  77
+    CustomReader.validates_absence_of :karma
  78
+
  79
+    p = CustomReader.new
  80
+    
  81
+    p[:karma] = "excellent"
  82
+    
  83
+    assert p.invalid?
  84
+
  85
+    assert_equal ["must be blank"], p.errors[:karma]
1

Same thing for blank lines here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/lib/active_model/errors.rb
... ...
@@ -324,6 +324,19 @@ def add_on_blank(attributes, options = {})
324 324
         add(attribute, :blank, options) if value.blank?
325 325
       end
326 326
     end
  327
+    
  328
+    # Will add an error message to each of the attributes in +attributes+ that
  329
+    # is not blank (using Object#blank?).
  330
+    #
  331
+    #   person.errors.add_on_not_blank(:name)
  332
+    #   person.errors.messages
  333
+    #   # => { :name => ["must be blank"] }
  334
+    def add_on_not_blank(attributes, options = {})
  335
+      [attributes].flatten.each do |attribute|
  336
+        value = @base.send(:read_attribute_for_validation, attribute)
  337
+        add(attribute, :not_blank, options) unless value.blank?
  338
+      end
  339
+    end
1

I don't think we need to have this in here, I believe that should be handled by the validator itself in this case. There's no need to extend Errors API with this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva

I wonder if we should use present instead of not_blank, thoughts?

@robotex82 thanks!

robotex82

Thank you guys!

Sorry, I have been busy all the time, but i'll have a look at your suggestions asap. Hopefully before next weekend.

Do you see any chance to have this merged into master for v4.0?

Carlos Antonio da Silva

@robotex82 I believe so :)

robotex82

I updated my local copy and changed add_on_not_blank to add_on_present.

I had some trouble with updating and rebasing - I admit, I lack experience on this topic - but I hope I didn't mess up.

Could someone please check my updated pull request?

What do I have to do next?

Carlos Antonio da Silva

@robotex82 thanks, but apparently you have some commits that shouldn't be there, can you try a new rebase, so that we can see only your PR diff?

robotex82

I did:

git rebase upstream/master
git push --force

is that correct?

Excuse my noobish questions! I googled for the correct process of updating forks, rebasing, etc. but it's still not really clear to me.

activemodel/lib/active_model/validations/absence.rb
... ...
@@ -0,0 +1,33 @@
  1
+require 'active_support/core_ext/object/blank'
  2
+
  3
+module ActiveModel
  4
+  module Validations
  5
+    # == Active Model Absence Validator
1

You can remove this comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/lib/active_model/errors.rb
... ...
@@ -328,6 +328,19 @@ def add_on_blank(attributes, options = {})
328 328
       end
329 329
     end
330 330
 
  331
+    # Will add an error message to each of the attributes in +attributes+ that
  332
+    # is present (using Object#present?).
  333
+    #
  334
+    #   person.errors.add_on_present(:name)
  335
+    #   person.errors.messages
  336
+    #   # => { :name => ["must be blank"] }
  337
+    def add_on_present(attributes, options = {})
  338
+      [attributes].flatten.each do |attribute|
1

Can use Array(attributes), it has changed these days in master.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
activemodel/lib/active_model/validations/absence.rb
... ...
@@ -0,0 +1,33 @@
  1
+require 'active_support/core_ext/object/blank'
  2
+
1

You don't need this require.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Carlos Antonio da Silva

@robotex82 it's fine now, thanks. I added some comments, and we'll need a two changelog entries in Active Model, one for Errors#add_on_present and other for the validation itself, can you please add them, and squash your commits? Thanks.

robotex82

Should I append or prepend to the changelog?

Carlos Antonio da Silva

@robotex82 prepend, always at top :).

robotex82 Add `ActiveModel::Validations::AbsenceValidator`, a validator to chec…
…k the absence of attributes.

Add `ActiveModel::Errors#add_on_present` method. Adds error messages to present attributes.
072b977
robotex82

Hope it's ok now.....

Carlos Antonio da Silva

I added some minor comments, can you take a look at them before we merge? Also, what do you think about adding an example for the changelog entries, similar to the ones you added to the docs? (just look at other changelog examples to see how to format them).

Other than that, just squash your commits and we're good to go :). Thanks!

robotex82

Well, I added the example to the changelog. But again I'm stuck with git :(

I have a commit history like that:

1a9f792 Updated changelog
072b977 Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the  absence of attributes. Add `ActiveModel::Errors#add_on_present` method. Adds error messages to presen
c35fa0b Removed blank lines Changed add_on_not_blank to add_on_present
02e8148 Added absence validator for active model.
c233b2f Fix guides home links and maintain compatibility with small screens

So I thought I google how to squash commits. I tried some things but I keep getting errors or the commits don't go away.

So, please, could some of you experieced people, tell me how I package things up?

Thanks, and sorry again...squashing, rabaseing, etc. is new stuff to me.

@carlosantoniodasilva: I couldn't find your comments, where are they?

Carlos Antonio da Silva

@robotex82 just look at the pull request Files Changed tab with the diff, and you'll see the comments (just ignore the "Remove blank line." ones).

Now about the rebase, Jon has just given a quick explanation here, but if you still have doubts, please let me know. Thanks!

Carlos Antonio da Silva

@steveklabnik Ah true, I forgot that, awesome thanks :heart:

Steve Klabnik
Collaborator

You didn't forget that at all; I saw this comment, was like "man, I really need to put this in a place where I can find it again," posted it, and then linked it here. ;)

Carlos Antonio da Silva

Haha sorry, I thought I had seen that among your other contribution related posts. Awesome anyway, you were fast on that :smile:

and others added some commits November 07, 2012
Carlos Antonio da Silva Remove block given check from private find_with_ids
This is already handled by #find, it's a duplicate check, since
find_with_ids is not called from anywhere else.
838c5e8
Carlos Antonio da Silva Use cached quoted_table_name instead of going through the connection 4995979
Carlos Antonio da Silva Remove not used indifferent_access requires from Base and FinderMethods fda107b
Yves Senn `plugin new` adds dummy app tasks when necessary.
Closes #8121

The `plugin new` generator always adds the dummy app rake tasks,
when a dummy app was created.
a085d27
Rafael Mendonça França Add CHANGELOG entry for #8108 on master too.
[ci skip]
6f8b95c
Add test to avoid regression of 58e48d5 50337da
Yves Senn test case to lock down the behavior of #7842 4d10d8b
Yves Senn routing prefix slash replacement is no longer necessary 4c14e05
James Coglan Store FlashHashes in the session as plain hashes rather than custom o…
…bjects with unstable class names and instance variables.

Refactor FlashHash to take values for its ivars in the constructor, to pretty up FlashHash.from_session_value.

Remove stale comment on FlashHash: it is no longer Marshaled in the session so we can change its implementation.

Remove blank lines I introduced in controller/test_case.rb.

Unit tests for FlashHash#to_session_value.

Put in a compatibility layer to accept FlashHash serializations from Rails 3.0+.

Test that Rails 3.2 session flashes are correctly converted to the new format.

Remove code path for processing Rails 3.0 FlashHashes since they can no longer deserialize.
f66ec25
Carlos Antonio da Silva Remove free usage of #tap 9a68393
robotex82 Add `ActiveModel::Validations::AbsenceValidator`, a validator to chec…
…k the absence of attributes.

Add `ActiveModel::Errors#add_on_present` method. Adds error messages to present attributes.
64df864
robotex82

This looks ....wrong?!

Carlos Antonio da Silva

Looks like there are some extra commits now :)

robotex82

OT: Time to read a book about GIT. Suggestions?

robotex82

I read the section on rebasing, but I don't get this right. :(

How do we get this right? Should I create a completely new pull request?

Steve Klabnik
Collaborator

@robotex82 Because it's been a month, and you're obviously having some trouble, and I had some time this morning, I took care of this.

You can see the commit here: d72a07f

I also took care of @frodsan 's comment about the 1.9 hash syntax, and double checked @carlosantoniodasilva 's comments about blank lines.

Thank you for your pull request, hopefully you will overcome the git beast next time ;)

Steve Klabnik steveklabnik closed this December 15, 2012
Wil Moore III

@robotex82: I highly recommend the Mastering Git series by @tlberglund and @matthewmccullough:

Full Disclosure: I know them both personally (and I appear in one of the videos) so I am a bit biased -- but trust me, watch these videos a few times through and you will level-up in your git-fu for sure.

robotex82

Awesome, thank you guys! :heart:

Sorry for the late answer, but I've been very busy!

While checking the commit, I discovered, that the CHANGELOG is messed up a little bit:

Actual:

*   Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the
    absence of attributes.

        class Person
          include ActiveModel::Validations

          attr_accessor :first_name
          validates_absence_of :first_name
        end

        person = Person.new
        person.first_name = "John"
        person.valid?
        # => false
        person.errors.messages
        # => {:first_name=>["must be blank"]}

    *Roberto Vasquez Angel*

*   `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`

Shouldn't it be:

    *Roberto Vasquez Angel*

*   Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the
    absence of attributes.

        class Person
          include ActiveModel::Validations

          attr_accessor :first_name
          validates_absence_of :first_name
        end

        person = Person.new
        person.first_name = "John"
        person.valid?
        # => false
        person.errors.messages
        # => {:first_name=>["must be blank"]}

*   `[attribute]_changed?` now returns `false` after a call to `reset_[attribute]!`

How do we get this right?

Thanks again!

Carlos Antonio da Silva

Not sure what you mean, but the name goes after the change, at the end.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 14 unique commits by 6 authors.

Nov 07, 2012
robotex82 Added absence validator for active model. 02e8148
robotex82 Removed blank lines
Changed add_on_not_blank to add_on_present
c35fa0b
robotex82 Add `ActiveModel::Validations::AbsenceValidator`, a validator to chec…
…k the absence of attributes.

Add `ActiveModel::Errors#add_on_present` method. Adds error messages to present attributes.
072b977
Nov 08, 2012
Carlos Antonio da Silva Remove block given check from private find_with_ids
This is already handled by #find, it's a duplicate check, since
find_with_ids is not called from anywhere else.
838c5e8
Carlos Antonio da Silva Use cached quoted_table_name instead of going through the connection 4995979
Carlos Antonio da Silva Remove not used indifferent_access requires from Base and FinderMethods fda107b
Yves Senn `plugin new` adds dummy app tasks when necessary.
Closes #8121

The `plugin new` generator always adds the dummy app rake tasks,
when a dummy app was created.
a085d27
Rafael Mendonça França Add CHANGELOG entry for #8108 on master too.
[ci skip]
6f8b95c
Add test to avoid regression of 58e48d5 50337da
Yves Senn test case to lock down the behavior of #7842 4d10d8b
Yves Senn routing prefix slash replacement is no longer necessary 4c14e05
James Coglan Store FlashHashes in the session as plain hashes rather than custom o…
…bjects with unstable class names and instance variables.

Refactor FlashHash to take values for its ivars in the constructor, to pretty up FlashHash.from_session_value.

Remove stale comment on FlashHash: it is no longer Marshaled in the session so we can change its implementation.

Remove blank lines I introduced in controller/test_case.rb.

Unit tests for FlashHash#to_session_value.

Put in a compatibility layer to accept FlashHash serializations from Rails 3.0+.

Test that Rails 3.2 session flashes are correctly converted to the new format.

Remove code path for processing Rails 3.0 FlashHashes since they can no longer deserialize.
f66ec25
Carlos Antonio da Silva Remove free usage of #tap 9a68393
robotex82 Add `ActiveModel::Validations::AbsenceValidator`, a validator to chec…
…k the absence of attributes.

Add `ActiveModel::Errors#add_on_present` method. Adds error messages to present attributes.
64df864
This page is out of date. Refresh to see the latest.
16  actionpack/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,21 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   Fix input name when `:multiple => true` and `:index` are set.
  4
+
  5
+    Before:
  6
+
  7
+        check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
  8
+        #=> <input name=\"post[foo][comment_ids]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids]\" type=\"checkbox\" value=\"1\" />
  9
+
  10
+    After:
  11
+
  12
+        check_box("post", "comment_ids", { :multiple => true, :index => "foo" }, 1)
  13
+        #=> <input name=\"post[foo][comment_ids][]\" type=\"hidden\" value=\"0\" /><input id=\"post_foo_comment_ids_1\" name=\"post[foo][comment_ids][]\" type=\"checkbox\" value=\"1\" />
  14
+
  15
+    Fix #8108
  16
+
  17
+    *Daniel Fox, Grant Hutchins & Trace Wax*
  18
+
3 19
 *   Clear url helpers when reloading routes.
4 20
 
5 21
     *Santiago Pastorino*
3  actionpack/lib/action_controller/test_case.rb
@@ -509,7 +509,7 @@ def process(action, http_method = 'GET', *args)
509 509
         @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
510 510
 
511 511
         @request.session.update(session) if session
512  
-        @request.session["flash"] = @request.flash.update(flash || {})
  512
+        @request.flash.update(flash || {})
513 513
 
514 514
         @controller.request  = @request
515 515
         @controller.response = @response
@@ -526,6 +526,7 @@ def process(action, http_method = 'GET', *args)
526 526
         @response.prepare!
527 527
 
528 528
         @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
  529
+        @request.session['flash'] = @request.flash.to_session_value
529 530
         @request.session.delete('flash') if @request.session['flash'].blank?
530 531
         @response
531 532
       end
33  actionpack/lib/action_dispatch/middleware/flash.rb
@@ -4,7 +4,7 @@ class Request < Rack::Request
4 4
     # read a notice you put there or <tt>flash["notice"] = "hello"</tt>
5 5
     # to put a new one.
6 6
     def flash
7  
-      @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new).tap(&:sweep)
  7
+      @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"])
8 8
     end
9 9
   end
10 10
 
@@ -70,16 +70,31 @@ def notice=(message)
70 70
       end
71 71
     end
72 72
 
73  
-    # Implementation detail: please do not change the signature of the
74  
-    # FlashHash class. Doing that will likely affect all Rails apps in
75  
-    # production as the FlashHash currently stored in their sessions will
76  
-    # become invalid.
77 73
     class FlashHash
78 74
       include Enumerable
79 75
 
80  
-      def initialize #:nodoc:
81  
-        @discard = Set.new
82  
-        @flashes = {}
  76
+      def self.from_session_value(value)
  77
+        flash = case value
  78
+                when FlashHash # Rails 3.1, 3.2
  79
+                  new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used))
  80
+                when Hash # Rails 4.0
  81
+                  new(value['flashes'], value['discard'])
  82
+                else
  83
+                  new
  84
+                end
  85
+
  86
+        flash.sweep
  87
+        flash
  88
+      end
  89
+
  90
+      def to_session_value
  91
+        return nil if empty?
  92
+        { 'discard' => @discard.to_a, 'flashes' => @flashes }
  93
+      end
  94
+
  95
+      def initialize(flashes = {}, discard = []) #:nodoc:
  96
+        @discard = Set.new(discard)
  97
+        @flashes = flashes
83 98
         @now     = nil
84 99
       end
85 100
 
@@ -223,7 +238,7 @@ def call(env)
223 238
 
224 239
       if flash_hash
225 240
         if !flash_hash.empty? || session.key?('flash')
226  
-          session["flash"] = flash_hash
  241
+          session["flash"] = flash_hash.to_session_value
227 242
           new_hash = flash_hash.dup
228 243
         else
229 244
           new_hash = flash_hash
4  actionpack/lib/action_dispatch/routing/mapper.rb
@@ -491,9 +491,7 @@ def define_generate_prefix(app, name)
491 491
                 prefix_options = options.slice(*_route.segment_keys)
492 492
                 # we must actually delete prefix segment keys to avoid passing them to next url_for
493 493
                 _route.segment_keys.each { |k| options.delete(k) }
494  
-                prefix = _routes.url_helpers.send("#{name}_path", prefix_options)
495  
-                prefix = '' if prefix == '/'
496  
-                prefix
  494
+                _routes.url_helpers.send("#{name}_path", prefix_options)
497 495
               end
498 496
             end
499 497
           end
21  actionpack/test/controller/flash_hash_test.rb
@@ -46,6 +46,27 @@ def test_to_hash
46 46
       assert_equal({'foo' => 'bar'}, @hash.to_hash)
47 47
     end
48 48
 
  49
+    def test_to_session_value
  50
+      @hash['foo'] = 'bar'
  51
+      assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => []}, @hash.to_session_value)
  52
+
  53
+      @hash.discard('foo')
  54
+      assert_equal({'flashes' => {'foo' => 'bar'}, 'discard' => %w[foo]}, @hash.to_session_value)
  55
+
  56
+      @hash.now['qux'] = 1
  57
+      assert_equal({'flashes' => {'foo' => 'bar', 'qux' => 1}, 'discard' => %w[foo qux]}, @hash.to_session_value)
  58
+
  59
+      @hash.sweep
  60
+      assert_equal(nil, @hash.to_session_value)
  61
+    end
  62
+
  63
+    def test_from_session_value
  64
+      rails_3_2_cookie = 'BAh7B0kiD3Nlc3Npb25faWQGOgZFRkkiJWY4ZTFiODE1MmJhNzYwOWMyOGJiYjE3ZWM5MjYzYmE3BjsAVEkiCmZsYXNoBjsARm86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsAOgxAY2xvc2VkRjoNQGZsYXNoZXN7BkkiDG1lc3NhZ2UGOwBGSSIKSGVsbG8GOwBGOglAbm93MA=='
  65
+      session = Marshal.load(Base64.decode64(rails_3_2_cookie))
  66
+      hash = Flash::FlashHash.from_session_value(session['flash'])
  67
+      assert_equal({'flashes' => {'message' => 'Hello'}, 'discard' => %w[message]}, hash.to_session_value)
  68
+    end
  69
+
49 70
     def test_empty?
50 71
       assert @hash.empty?
51 72
       @hash['zomg'] = 'bears'
5  actionpack/test/dispatch/prefix_generation_test.rb
@@ -241,6 +241,11 @@ def setup
241 241
       assert_equal "/something/", app_object.root_path
242 242
     end
243 243
 
  244
+    test "[OBJECT] generating application's route includes default_url_options[:trailing_slash]" do
  245
+      RailsApplication.routes.default_url_options[:trailing_slash] = true
  246
+      assert_equal "/awesome/blog/posts", engine_object.posts_path
  247
+    end
  248
+
244 249
     test "[OBJECT] generating engine's route with url_for" do
245 250
       path = engine_object.url_for(:controller => "inside_engine_generating",
246 251
                                    :action => "show",
18  activemodel/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,23 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   Add `ActiveModel::Validations::AbsenceValidator`, a validator to check the
  4
+    absence of attributes.
  5
+
  6
+        class Person < ActiveRecord::Base
  7
+          validates_absence_of :first_name
  8
+        end
  9
+
  10
+        person = Person.new
  11
+        person.first_name = "John"
  12
+        person.valid?
  13
+        => false
  14
+        # first_name must be blank
  15
+
  16
+*   Added `ActiveModel::Errors#add_on_present` method. Adds error messages to
  17
+    present attributes.
  18
+
  19
+    *Roberto Vasquez Angel*
  20
+
3 21
 *   Add `ActiveModel::ForbiddenAttributesProtection`, a simple module to
4 22
     protect attributes from mass assignment when non-permitted attributes are passed.
5 23
 
13  activemodel/lib/active_model/errors.rb
@@ -328,6 +328,19 @@ def add_on_blank(attributes, options = {})
328 328
       end
329 329
     end
330 330
 
  331
+    # Will add an error message to each of the attributes in +attributes+ that
  332
+    # is present (using Object#present?).
  333
+    #
  334
+    #   person.errors.add_on_present(:name)
  335
+    #   person.errors.messages
  336
+    #   # => { :name => ["must be blank"] }
  337
+    def add_on_present(attributes, options = {})
  338
+      Array(attributes).flatten.each do |attribute|
  339
+        value = @base.send(:read_attribute_for_validation, attribute)
  340
+        add(attribute, :not_blank, options) if value.present?
  341
+      end
  342
+    end
  343
+
331 344
     # Returns +true+ if an error on the attribute with the given message is
332 345
     # present, +false+ otherwise. +message+ is treated the same as for +add+.
333 346
     #
1  activemodel/lib/active_model/locale/en.yml
@@ -13,6 +13,7 @@ en:
13 13
       accepted: "must be accepted"
14 14
       empty: "can't be empty"
15 15
       blank: "can't be blank"
  16
+      not_blank: "must be blank"      
16 17
       too_long: "is too long (maximum is %{count} characters)"
17 18
       too_short: "is too short (minimum is %{count} characters)"
18 19
       wrong_length: "is the wrong length (should be %{count} characters)"
31  activemodel/lib/active_model/validations/absence.rb
... ...
@@ -0,0 +1,31 @@
  1
+module ActiveModel
  2
+  module Validations
  3
+    # == Active Model Absence Validator
  4
+    class AbsenceValidator < EachValidator #:nodoc:
  5
+      def validate(record)
  6
+        record.errors.add_on_present(attributes, options)
  7
+      end
  8
+    end
  9
+
  10
+    module HelperMethods
  11
+      # Validates that the specified attributes are blank (as defined by
  12
+      # Object#blank?). Happens by default on save.
  13
+      #
  14
+      #   class Person < ActiveRecord::Base
  15
+      #     validates_absence_of :first_name
  16
+      #   end
  17
+      #
  18
+      # The first_name attribute must be in the object and it must be blank.
  19
+      #
  20
+      # Configuration options:
  21
+      # * <tt>:message</tt> - A custom error message (default is: "must be blank").
  22
+      #
  23
+      # There is also a list of default options supported by every validator:
  24
+      # +:if+, +:unless+, +:on+ and +:strict+.
  25
+      # See <tt>ActiveModel::Validation#validates</tt> for more information
  26
+      def validates_absence_of(*attr_names)
  27
+        validates_with AbsenceValidator, _merge_attributes(attr_names)
  28
+      end
  29
+    end
  30
+  end
  31
+end
67  activemodel/test/cases/validations/absence_validation_test.rb
... ...
@@ -0,0 +1,67 @@
  1
+# encoding: utf-8
  2
+require 'cases/helper'
  3
+require 'models/topic'
  4
+require 'models/person'
  5
+require 'models/custom_reader'
  6
+
  7
+class AbsenceValidationTest < ActiveModel::TestCase
  8
+  teardown do
  9
+    Topic.reset_callbacks(:validate)
  10
+    Person.reset_callbacks(:validate)
  11
+    CustomReader.reset_callbacks(:validate)
  12
+  end
  13
+
  14
+  def test_validate_absences
  15
+    Topic.validates_absence_of(:title, :content)
  16
+    t = Topic.new
  17
+    t.title = "foo"
  18
+    t.content = "bar"
  19
+    assert t.invalid?
  20
+    assert_equal ["must be blank"], t.errors[:title]
  21
+    assert_equal ["must be blank"], t.errors[:content]
  22
+    t.title = ""
  23
+    t.content  = "something"
  24
+    assert t.invalid?
  25
+    assert_equal ["must be blank"], t.errors[:content]
  26
+    t.content = ""
  27
+    assert t.valid?
  28
+  end
  29
+
  30
+  def test_accepts_array_arguments
  31
+    Topic.validates_absence_of %w(title content)
  32
+    t = Topic.new
  33
+    t.title = "foo"
  34
+    t.content = "bar"
  35
+    assert t.invalid?
  36
+    assert_equal ["must be blank"], t.errors[:title]
  37
+    assert_equal ["must be blank"], t.errors[:content]
  38
+  end
  39
+
  40
+  def test_validates_acceptance_of_with_custom_error_using_quotes
  41
+    Person.validates_absence_of :karma, :message => "This string contains 'single' and \"double\" quotes"
  42
+    p = Person.new
  43
+    p.karma = "good"
  44
+    assert p.invalid?
  45
+    assert_equal "This string contains 'single' and \"double\" quotes", p.errors[:karma].last
  46
+  end
  47
+
  48
+  def test_validates_absence_of_for_ruby_class
  49
+    Person.validates_absence_of :karma
  50
+    p = Person.new
  51
+    p.karma = "good"
  52
+    assert p.invalid?
  53
+    assert_equal ["must be blank"], p.errors[:karma]
  54
+    p.karma = nil
  55
+    assert p.valid?
  56
+  end
  57
+
  58
+  def test_validates_absence_of_for_ruby_class_with_custom_reader
  59
+    CustomReader.validates_absence_of :karma
  60
+    p = CustomReader.new
  61
+    p[:karma] = "excellent"
  62
+    assert p.invalid?
  63
+    assert_equal ["must be blank"], p.errors[:karma]
  64
+    p[:karma] = ""
  65
+    assert p.valid?
  66
+  end
  67
+end
1  activerecord/lib/active_record/base.rb
@@ -8,7 +8,6 @@
8 8
 require 'active_support/core_ext/class/delegating_attributes'
9 9
 require 'active_support/core_ext/array/extract_options'
10 10
 require 'active_support/core_ext/hash/deep_merge'
11  
-require 'active_support/core_ext/hash/indifferent_access'
12 11
 require 'active_support/core_ext/hash/slice'
13 12
 require 'active_support/core_ext/string/behavior'
14 13
 require 'active_support/core_ext/kernel/singleton_class'
6  activerecord/lib/active_record/relation/finder_methods.rb
... ...
@@ -1,5 +1,3 @@
1  
-require 'active_support/core_ext/hash/indifferent_access'
2  
-
3 1
 module ActiveRecord
4 2
   module FinderMethods
5 3
     # Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
@@ -225,7 +223,7 @@ def apply_join_dependency(relation, join_dependency)
225 223
 
226 224
     def construct_limited_ids_condition(relation)
227 225
       orders = relation.order_values.map { |val| val.presence }.compact
228  
-      values = @klass.connection.distinct("#{@klass.connection.quote_table_name table_name}.#{primary_key}", orders)
  226
+      values = @klass.connection.distinct("#{quoted_table_name}.#{primary_key}", orders)
229 227
 
230 228
       relation = relation.dup
231 229
 
@@ -234,8 +232,6 @@ def construct_limited_ids_condition(relation)
234 232
     end
235 233
 
236 234
     def find_with_ids(*ids)
237  
-      return to_a.find { |*block_args| yield(*block_args) } if block_given?
238  
-
239 235
       expects_array = ids.first.kind_of?(Array)
240 236
       return ids.first if expects_array && ids.first.empty?
241 237
 
11  activerecord/test/cases/nested_attributes_test.rb
@@ -185,6 +185,17 @@ def parrot_attributes=(attrs)
185 185
     assert_equal "James", mean_pirate.parrot.name
186 186
     assert_equal "blue", mean_pirate.parrot.color
187 187
   end
  188
+
  189
+  def test_accepts_nested_attributes_for_can_be_overridden_in_subclasses
  190
+    Pirate.accepts_nested_attributes_for(:parrot)
  191
+
  192
+    mean_pirate_class = Class.new(Pirate) do
  193
+      accepts_nested_attributes_for :parrot
  194
+    end
  195
+    mean_pirate = mean_pirate_class.new
  196
+    mean_pirate.parrot_attributes = { :name => "James" }
  197
+    assert_equal "James", mean_pirate.parrot.name
  198
+  end
188 199
 end
189 200
 
190 201
 class TestNestedAttributesOnAHasOneAssociation < ActiveRecord::TestCase
7  railties/CHANGELOG.md
Source Rendered
... ...
@@ -1,5 +1,10 @@
1 1
 ## Rails 4.0.0 (unreleased) ##
2 2
 
  3
+*   Add dummy app Rake tasks when --skip-test-unit and --dummy-path is passed to the plugin generator.
  4
+    Fix #8121
  5
+
  6
+    *Yves Senn*
  7
+
3 8
 *   Ensure that RAILS_ENV is set when accessing Rails.env *Steve Klabnik*
4 9
 
5 10
 *   Don't eager-load app/assets and app/views *Elia Schito*
@@ -9,7 +14,7 @@
9 14
 *   New test locations `test/models`, `test/helpers`, `test/controllers`, and
10 15
     `test/mailers`. Corresponding rake tasks added as well. *Mike Moore*
11 16
 
12  
-*   Set a different cache per environment for assets pipeline 
  17
+*   Set a different cache per environment for assets pipeline
13 18
     through `config.assets.cache`.
14 19
 
15 20
     *Guillermo Iguaran*
6  railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb
@@ -225,7 +225,7 @@ def create_test_files
225 225
       end
226 226
 
227 227
       def create_test_dummy_files
228  
-        return if options[:skip_test_unit] && options[:dummy_path] == 'test/dummy'
  228
+        return unless with_dummy_app?
229 229
         create_dummy_app
230 230
       end
231 231
 
@@ -279,6 +279,10 @@ def mountable?
279 279
         options[:mountable]
280 280
       end
281 281
 
  282
+      def with_dummy_app?
  283
+        options[:skip_test_unit].blank? || options[:dummy_path] != 'test/dummy'
  284
+      end
  285
+
282 286
       def self.banner
283 287
         "rails plugin new #{self.arguments.map(&:usage).join(' ')} [options]"
284 288
       end
2  railties/lib/rails/generators/rails/plugin_new/templates/Rakefile
@@ -14,7 +14,7 @@ RDoc::Task.new(:rdoc) do |rdoc|
14 14
   rdoc.rdoc_files.include('lib/**/*.rb')
15 15
 end
16 16
 
17  
-<% if full? && !options[:skip_active_record] && !options[:skip_test_unit] -%>
  17
+<% if full? && !options[:skip_active_record] && with_dummy_app? -%>
18 18
 APP_RAKEFILE = File.expand_path("../<%= dummy_path -%>/Rakefile", __FILE__)
19 19
 load 'rails/tasks/engine.rake'
20 20
 <% end %>
6  railties/test/generators/plugin_new_generator_test.rb
@@ -66,6 +66,12 @@ def test_generating_test_files_in_full_mode_without_unit_test_files
66 66
     assert_no_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
67 67
   end
68 68
 
  69
+  def test_generating_adds_dummy_app_rake_tasks_without_unit_test_files
  70
+    run_generator [destination_root, "-T", "--mountable", '--dummy-path', 'my_dummy_app']
  71
+
  72
+    assert_match(/APP_RAKEFILE/, File.read(File.join(destination_root, "Rakefile")))
  73
+  end
  74
+
69 75
   def test_ensure_that_plugin_options_are_not_passed_to_app_generator
70 76
     FileUtils.cd(Rails.root)
71 77
     assert_no_match(/It works from file!.*It works_from_file/, run_generator([destination_root, "-m", "lib/template.rb"]))
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.