aboutsummaryrefslogtreecommitdiffstats
path: root/activemodel
diff options
context:
space:
mode:
Diffstat (limited to 'activemodel')
-rw-r--r--activemodel/CHANGELOG.md9
-rw-r--r--activemodel/activemodel.gemspec2
-rw-r--r--activemodel/examples/validations.rb1
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activemodel/lib/active_model/naming.rb12
-rw-r--r--activemodel/lib/active_model/secure_password.rb3
-rw-r--r--activemodel/lib/active_model/validations/clusivity.rb16
-rw-r--r--activemodel/test/cases/dirty_test.rb17
-rw-r--r--activemodel/test/cases/secure_password_test.rb8
-rw-r--r--activemodel/test/cases/validations/inclusion_validation_test.rb22
-rw-r--r--activemodel/test/models/topic.rb2
11 files changed, 77 insertions, 17 deletions
diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md
index 91b25d5cda..e8602ecbcf 100644
--- a/activemodel/CHANGELOG.md
+++ b/activemodel/CHANGELOG.md
@@ -1,3 +1,12 @@
+* Fix `has_secure_password` to honor bcrypt-ruby's cost attribute.
+
+ *T.J. Schuck*
+
+* Updated the `ActiveModel::Dirty#changed_attributes` method to be indifferent between using
+ symbols and strings as keys.
+
+ *William Myers*
+
* Added new API methods `reset_changes` and `changed_applied` to `ActiveModel::Dirty`
that control changes state. Previsously you needed to update internal
instance variables, but now API methods are available.
diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec
index 51655fe3da..11e755649c 100644
--- a/activemodel/activemodel.gemspec
+++ b/activemodel/activemodel.gemspec
@@ -5,7 +5,7 @@ Gem::Specification.new do |s|
s.name = 'activemodel'
s.version = version
s.summary = 'A toolkit for building modeling frameworks (part of Rails).'
- s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, observers, serialization, internationalization, and testing.'
+ s.description = 'A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing.'
s.required_ruby_version = '>= 1.9.3'
diff --git a/activemodel/examples/validations.rb b/activemodel/examples/validations.rb
index c94cd17e18..b8e74acd5e 100644
--- a/activemodel/examples/validations.rb
+++ b/activemodel/examples/validations.rb
@@ -1,3 +1,4 @@
+require File.expand_path('../../../load_paths', __FILE__)
require 'active_model'
class Person
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 98e7d84608..c5f1b3f11a 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -145,7 +145,7 @@ module ActiveModel
# person.name = 'robert'
# person.changed_attributes # => {"name" => "bob"}
def changed_attributes
- @changed_attributes ||= {}
+ @changed_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
end
# Handle <tt>*_changed?</tt> for +method_missing+.
diff --git a/activemodel/lib/active_model/naming.rb b/activemodel/lib/active_model/naming.rb
index bc9edf4a56..198efc5088 100644
--- a/activemodel/lib/active_model/naming.rb
+++ b/activemodel/lib/active_model/naming.rb
@@ -262,10 +262,10 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
- # ActiveModel::Naming.singular_route_key(Blog::Post) #=> post
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> "post"
#
# # For shared engine:
- # ActiveModel::Naming.singular_route_key(Blog::Post) #=> blog_post
+ # ActiveModel::Naming.singular_route_key(Blog::Post) #=> "blog_post"
def self.singular_route_key(record_or_class)
model_name_from_record_or_class(record_or_class).singular_route_key
end
@@ -274,10 +274,10 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> posts
+ # ActiveModel::Naming.route_key(Blog::Post) #=> "posts"
#
# # For shared engine:
- # ActiveModel::Naming.route_key(Blog::Post) #=> blog_posts
+ # ActiveModel::Naming.route_key(Blog::Post) #=> "blog_posts"
#
# The route key also considers if the noun is uncountable and, in
# such cases, automatically appends _index.
@@ -289,10 +289,10 @@ module ActiveModel
# namespaced models regarding whether it's inside isolated engine.
#
# # For isolated engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> post
+ # ActiveModel::Naming.param_key(Blog::Post) #=> "post"
#
# # For shared engine:
- # ActiveModel::Naming.param_key(Blog::Post) #=> blog_post
+ # ActiveModel::Naming.param_key(Blog::Post) #=> "blog_post"
def self.param_key(record_or_class)
model_name_from_record_or_class(record_or_class).param_key
end
diff --git a/activemodel/lib/active_model/secure_password.rb b/activemodel/lib/active_model/secure_password.rb
index 17fafe4be9..7e694b5c50 100644
--- a/activemodel/lib/active_model/secure_password.rb
+++ b/activemodel/lib/active_model/secure_password.rb
@@ -46,7 +46,6 @@ module ActiveModel
# This is to avoid ActiveModel (and by extension the entire framework)
# being dependent on a binary library.
begin
- gem 'bcrypt-ruby', '~> 3.1.2'
require 'bcrypt'
rescue LoadError
$stderr.puts "You don't have bcrypt-ruby installed in your application. Please add it to your Gemfile and run bundle install"
@@ -103,7 +102,7 @@ module ActiveModel
def password=(unencrypted_password)
unless unencrypted_password.blank?
@password = unencrypted_password
- cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine::DEFAULT_COST
+ cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
self.password_digest = BCrypt::Password.create(unencrypted_password, cost: cost)
end
end
diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb
index 1c35cb7c35..fd6cc1edb4 100644
--- a/activemodel/lib/active_model/validations/clusivity.rb
+++ b/activemodel/lib/active_model/validations/clusivity.rb
@@ -30,12 +30,18 @@ module ActiveModel
@delimiter ||= options[:in] || options[:within]
end
- # In Ruby 1.9 <tt>Range#include?</tt> on non-numeric ranges checks all possible values in the
- # range for equality, which is slower but more accurate. <tt>Range#cover?</tt> uses
- # the previous logic of comparing a value with the range endpoints, which is fast
- # but is only accurate on numeric ranges.
+ # In Ruby 1.9 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
+ # possible values in the range for equality, which is slower but more accurate.
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
+ # endpoints, which is fast but is only accurate on Numeric, Time, or DateTime ranges.
def inclusion_method(enumerable)
- (enumerable.is_a?(Range) && enumerable.first.is_a?(Numeric)) ? :cover? : :include?
+ return :include? unless enumerable.is_a?(Range)
+ case enumerable.first
+ when Numeric, Time, DateTime
+ :cover?
+ else
+ :include?
+ end
end
end
end
diff --git a/activemodel/test/cases/dirty_test.rb b/activemodel/test/cases/dirty_test.rb
index ed34ca8a6e..a90d0b1299 100644
--- a/activemodel/test/cases/dirty_test.rb
+++ b/activemodel/test/cases/dirty_test.rb
@@ -3,11 +3,12 @@ require "cases/helper"
class DirtyTest < ActiveModel::TestCase
class DirtyModel
include ActiveModel::Dirty
- define_attribute_methods :name, :color
+ define_attribute_methods :name, :color, :size
def initialize
@name = nil
@color = nil
+ @size = nil
end
def name
@@ -28,6 +29,15 @@ class DirtyTest < ActiveModel::TestCase
@color = val
end
+ def size
+ @size
+ end
+
+ def size=(val)
+ attribute_will_change!(:size) unless val == @size
+ @size = val
+ end
+
def save
changes_applied
end
@@ -124,4 +134,9 @@ class DirtyTest < ActiveModel::TestCase
assert_equal ["Otto", "Mr. Manfredgensonton"], @model.name_change
assert_equal @model.name_was, "Otto"
end
+
+ test "using attribute_will_change! with a symbol" do
+ @model.size = 1
+ assert @model.size_changed?
+ end
end
diff --git a/activemodel/test/cases/secure_password_test.rb b/activemodel/test/cases/secure_password_test.rb
index 98e5c747d5..41d0b2263e 100644
--- a/activemodel/test/cases/secure_password_test.rb
+++ b/activemodel/test/cases/secure_password_test.rb
@@ -82,6 +82,14 @@ class SecurePasswordTest < ActiveModel::TestCase
assert_equal BCrypt::Engine::DEFAULT_COST, @user.password_digest.cost
end
+ test "Password digest cost honors bcrypt cost attribute when min_cost is false" do
+ ActiveModel::SecurePassword.min_cost = false
+ BCrypt::Engine.cost = 5
+
+ @user.password = "secret"
+ assert_equal BCrypt::Engine.cost, @user.password_digest.cost
+ end
+
test "Password digest cost can be set to bcrypt min cost to speed up tests" do
ActiveModel::SecurePassword.min_cost = true
diff --git a/activemodel/test/cases/validations/inclusion_validation_test.rb b/activemodel/test/cases/validations/inclusion_validation_test.rb
index 01a373d85d..8b90856869 100644
--- a/activemodel/test/cases/validations/inclusion_validation_test.rb
+++ b/activemodel/test/cases/validations/inclusion_validation_test.rb
@@ -1,5 +1,6 @@
# encoding: utf-8
require 'cases/helper'
+require 'active_support/all'
require 'models/topic'
require 'models/person'
@@ -20,6 +21,27 @@ class InclusionValidationTest < ActiveModel::TestCase
assert Topic.new("title" => "bbb", "content" => "abc").valid?
end
+ def test_validates_inclusion_of_time_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.ago..Time.now)
+ assert Topic.new(title: 'aaa', created_at: 2.years.ago).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.ago).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.from_now).invalid?
+ end
+
+ def test_validates_inclusion_of_date_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.until(Date.today)..Date.today)
+ assert Topic.new(title: 'aaa', created_at: 2.years.until(Date.today)).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.until(Date.today)).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.since(Date.today)).invalid?
+ end
+
+ def test_validates_inclusion_of_date_time_range
+ Topic.validates_inclusion_of(:created_at, in: 1.year.until(DateTime.current)..DateTime.current)
+ assert Topic.new(title: 'aaa', created_at: 2.years.until(DateTime.current)).invalid?
+ assert Topic.new(title: 'aaa', created_at: 3.months.until(DateTime.current)).valid?
+ assert Topic.new(title: 'aaa', created_at: 37.weeks.since(DateTime.current)).invalid?
+ end
+
def test_validates_inclusion_of
Topic.validates_inclusion_of(:title, in: %w( a b c d e f g ))
diff --git a/activemodel/test/models/topic.rb b/activemodel/test/models/topic.rb
index c9af78f595..1411a093e9 100644
--- a/activemodel/test/models/topic.rb
+++ b/activemodel/test/models/topic.rb
@@ -6,7 +6,7 @@ class Topic
super | [ :message ]
end
- attr_accessor :title, :author_name, :content, :approved
+ attr_accessor :title, :author_name, :content, :approved, :created_at
attr_accessor :after_validation_performed
after_validation :perform_after_validation