The thread_safe gem is being deprecated and all its code has been merged
into the concurrent-ruby gem. The new class, Concurrent::Map, is exactly
the same as its predecessor except for fixes to two bugs discovered
during the merge.
Example:
```ruby
class Person
include ActiveModel::Validations
attr_reader :name, :title
validates_presence_of :name, on: :create
validates_presence_of :title, on: :update
end
person = Person.new
person.valid?([:create, :update]) # => true
person.errors.messages # => {:name=>["can't be blank"], :title=>["can't be blank"]}
```
This reverts commit 51dd2588433457960cca592d5b5dac6e0537feac, reversing
changes made to ecb4e4b21b3222b823fa24d4a0598b1f2f63ecfb.
This broke Active Record tests
It was not expecting the new `case_insensitive` option to be passed to
`generate_message`, instead of fixing the test we can just not pass this
option down since it is specific to the confirmation validator and not
necessary for the error message.
Case :- 1. In case of email confirmation one needs case insensitive comparison
2. In case of password confirmation one needs case sensitive comparison
[ci skip] Update Guides for case_sensitive option in confirmation validation
Example:
```ruby
class Person
include ActiveModel::Validations
attr_reader :name, :title
validates_presence_of :name, on: :create
validates_presence_of :title, on: :update
end
person = Person.new
person.valid?([:create, :update]) # => true
person.errors.messages # => {:name=>["can't be blank"], :title=>["can't be blank"]}
```
I wrote a utility that helps find areas where you could optimize your program using a frozen string instead of a string literal, it's called [let_it_go](https://github.com/schneems/let_it_go). After going through the output and adding `.freeze` I was able to eliminate the creation of 1,114 string objects on EVERY request to [codetriage](codetriage.com). How does this impact execution?
To look at memory:
```ruby
require 'get_process_mem'
mem = GetProcessMem.new
GC.start
GC.disable
1_114.times { " " }
before = mem.mb
after = mem.mb
GC.enable
puts "Diff: #{after - before} mb"
```
Creating 1,114 string objects results in `Diff: 0.03125 mb` of RAM allocated on every request. Or 1mb every 32 requests.
To look at raw speed:
```ruby
require 'benchmark/ips'
number_of_objects_reduced = 1_114
Benchmark.ips do |x|
x.report("freeze") { number_of_objects_reduced.times { " ".freeze } }
x.report("no-freeze") { number_of_objects_reduced.times { " " } }
end
```
We get the results
```
Calculating -------------------------------------
freeze 1.428k i/100ms
no-freeze 609.000 i/100ms
-------------------------------------------------
freeze 14.363k (± 8.5%) i/s - 71.400k
no-freeze 6.084k (± 8.1%) i/s - 30.450k
```
Now we can do some maths:
```ruby
ips = 6_226k # iterations / 1 second
call_time_before = 1.0 / ips # seconds per iteration
ips = 15_254 # iterations / 1 second
call_time_after = 1.0 / ips # seconds per iteration
diff = call_time_before - call_time_after
number_of_objects_reduced * diff * 100
# => 0.4530373333993266 miliseconds saved per request
```
So we're shaving off 1 second of execution time for every 220 requests.
Is this going to be an insane speed boost to any Rails app: nope. Should we merge it: yep.
p.s. If you know of a method call that doesn't modify a string input such as [String#gsub](b0e2da69f0/lib/let_it_go/core_ext/string.rb (L37)) please [give me a pull request to the appropriate file](b0e2da69f0/lib/let_it_go/core_ext/string.rb (L37)), or open an issue in LetItGo so we can track and freeze more strings.
Keep those strings Frozen
![](https://www.dropbox.com/s/z4dj9fdsv213r4v/let-it-go.gif?dl=1)
This fixes the issue where you may be comparing (using a numeric
validator such as `greater_than`) numbers of a specific Numeric type
such as `BigDecimal`.
Previous behavior took the numeric value to be validated and
unconditionally converted to Float. For example, due to floating point
precision, this can cause issues when comparing a Float to a BigDecimal.
Consider the following:
```
validates :sub_total, numericality: {
greater_than: BigDecimal('97.18')
}
```
If the `:sub_total` value BigDecimal.new('97.18') was validated against
the above, the following would be valid since `:sub_total` is converted
to a Float regardless of its original type. The result therefore becomes
Kernel.Float(97.18) > BigDecimal.new('97.18')
The above illustrated behavior is corrected with this patch by
conditionally converting the value to validate to float.
Use the post-type-cast version of the attribute to validate numericality
[Roque Pinel & Trevor Wistaff]
[ci skip]
Closes#20792.
Custom validation methods are implemented in terms of
callbacks. The `validate` callback chain can't be halted using return
values of individual callbacks.
[ci skip]
While this :nodoc: did hide the constant it also removed the following
methods from the API docs:
- #attribute_method?
- #clear_validators!
- #validate
- #validators
- #validators_on
Those are public API and should be visible.
Issue was caused by dee4fbc
/cc @zzak
Only one constraint option can be used at a time (except for the minimum
and maximum ones that can eventually be combined). However, other
options can be used with them (e.g. the validation failure message).
So let's make the distinction between these two different options
categories.
[Yves Senn, Matthew Draper & Robin Dupret]
`ActiveModel::Dirty#[attr_name]_previous_change` to improve access
to recorded changes after the model has been saved.
It makes the dirty-attributes query methods consistent before and after
saving.
As demonstrated by #19570, this option is severely limited, and
satisfies an extremely specific use case. Realistically, there's not
much reason for this option to exist. Its functionality can be trivially
replicated with a normal Ruby method. Let's deprecate this option, in
favor of the simpler solution.
Rails 5.0 changes to ActiveModel::Errors include addition of `details`
that also accidentally changed the return value of `delete`. Since
there was no test for that behavior it went unnoticed. This commit
adds a test and fixes the regression.
Small improvements to comments have also been made. Since `get` is
getting deprecated it is better to use `[]` in other methods' code
examples. Also, in the module usage example, `def Person.method`
was replaced with a more commonly used `def self.method` code style.
Deprecation messages in ActiveModel::Errors are using String#squish
from ActiveSupport but were not explicitly requiring it, causing failures
when used outside rails.
The name `ActiveModel::AttributeAssignment::UnknownAttributeError` is
too implementation specific so let's move the constant directly under
the ActiveModel namespace.
Also since this constant used to be under the ActiveRecord namespace, to
make the upgrade path easier, let's avoid raising the former constant
when we deal with this error on the Active Record side.
Previously, calling `User.model_name.to_json` would result in an infinite
recursion as `.model_name` inherited its `.as_json` behavior from Object. This
patch fixes that unexpected behavior by delegating `.as_json` to :name.
The existing example seems somewhat forced: is it realistic
to have a model that accepts state in its initializer but
considers it has not been changed? By allowing state changes
to happen only via accessors it seems more natural that new
instances are considered to be unchanged (as they are in AR).
[ci skip]
Fixes#17621. This 5 year old (or older) issue causes validations to fire
when a parent record has `validate: false` option and a child record is
saved. It's not the responsibility of the model to validate an
associated object unless the object was created or modified by the
parent.
Clean up tests related to validations
`assert_nothing_raised` is not benefiting us in these tests
Corrected spelling of "respects"
It's better to use `assert_not_operator` over `assert !r.valid`
To be able to return type of validator, one can now call `details`
on Errors instance:
```ruby
class User < ActiveRecord::Base
validates :name, presence: true
end
```
```ruby
user = User.new; user.valid?; user.errors.details
=> {name: [{error: :blank}]}
```
When an attribute is assigned, we determine if it was already marked as
changed so we can determine if we need to clear the changes, or mark it
as changed. Since this only affects the `attributes_changed_by_setter`
hash, in-place changes are irrelevant to this process. Since calculating
in-place changes can be expensive, we can just skip it here.
I also added a test for the only edge case I could think of that would
be affected by this change.
This commit changes the original documentation of ActiveModel::Lint::Tests
introduced in dbf20c2d to focus less on *why* the tests exist and more on
*what* the tests do.
For instance, `test_to_key` was documented as:
> Returns an Enumerable of all (primary) key attributes...
whereas `test_to_key` is simply a test meant to *fail* or *pass*, and the
documentation above refers to `to_key`.
[ci skip]
Introduce explicit way of halting callback chains by throwing :abort. Deprecate current implicit behavior of halting callback chains by returning `false` in apps ported to Rails 5.0. Completely remove that behavior in brand new Rails 5.0 apps.
Conflicts:
railties/CHANGELOG.md
Before this commit, returning `false` in an ActiveModel `before_` callback
such as `before_create` would halt the callback chain.
After this commit, the behavior is deprecated: will still work until
the next release of Rails but will also display a deprecation warning.
The preferred way to halt a callback chain is to explicitly `throw(:abort)`.
Before this commit, returning `false` in an ActiveModel validation
callback such as `before_validation` would halt the callback chain.
After this commit, the behavior is deprecated: will still work until
the next release of Rails but will also display a deprecation warning.
The preferred way to halt a callback chain is to explicitly `throw(:abort)`.
This commit changes arguments and default value of CallbackChain's :terminator
option.
After this commit, Chains of callbacks defined **without** an explicit
`:terminator` option will be halted as soon as a `before_` callback throws
`:abort`.
Chains of callbacks defined **with** a `:terminator` option will maintain their
existing behavior of halting as soon as a `before_` callback matches the
terminator's expectation. For instance, ActiveModel's callbacks will still
halt the chain when a `before_` callback returns `false`.
Calling `changed_attributes` will ultimately check if every mutable
attribute has changed in place. Since this gets called whenever an
attribute is assigned, it's extremely slow. Instead, we can avoid this
calculation until we actually need it.
Fixes#18029
The default value for the argument `message` in
`ActiveModel::Errors#add` has a new behavior
since ca99ab2481d44d67bc392d0ec1125ff1439e9f94.
Before
person.errors.add(:name, nil)
# => ["is invalid"]
After
person.errors.add(:name, nil)
# => [nil]
I'm not sure what's the use case for this, but apparently it broke some apps.
Since it was not the intended result from #16210 I fixed it to not raise an
exception anymore. However, I didn't add documentation for it because I don't
know if this should be officially supported without knowing how it's meant to
be used.
In general, validations should be side-effect-free (other than adding to the
error message to `@errors`). Order-dependent validations seems like a bad idea.
Fixes#18002
Active Record defines `attribute_method_suffix :?`. That suffix will
match any predicate method when the lookup occurs in Active Model. This
will make it incorrectly decide that `id_changed?` should not exist,
because it attempts to determine if the attribute `id_changed` is
present, rather than `id` with the `_changed?` suffix. Instead, we will
look for any correct match.
The detection of in-place changes caused a weird unexpected issue with
numericality validations. That validator (out of necessity) works on the
`_before_type_cast` version of the attribute, since on an `:integer`
type column, a non-numeric string would type cast to 0.
However, strings are mutable, and we changed strings to ensure that the
post type cast version of the attribute was a different instance than
the before type cast version (so the mutation detection can work
properly).
Even though strings are the only mutable type for which a numericality
validation makes sense, special casing strings would feel like a strange
change to make here. Instead, we can make the assumption that for all
mutable types, we should work on the post-type-cast version of the
attribute, since all cases which would return 0 for non-numeric strings
are immutable.
Fixes#17852
In Rails 4.2 it is impossible to define a custom default value for a model's
attribute without making it appear as _changed?, especially when the model
is first initialized. Making this method publicly visible will allow such a behaviour,
without the need to use private APIs.
This patch uniformizes warning messages. I used the most common style
already present in the code base:
* Capitalize the first word.
* End the message with a full stop.
* "Rails 5" instead of "Rails 5.0".
* Backticks for method names and inline code.
Also, converted a few long strings into the new heredoc convention.
Inspired by @tenderlove's work in
c363fff29f060e6a2effe1e4bb2c4dd4cd805d6e, this reduces the number of
strings allocated when running callbacks for ActiveRecord instances. I
measured that using this script:
```
require 'objspace'
require 'active_record'
require 'allocation_tracer'
ActiveRecord::Base.establish_connection adapter: "sqlite3",
database: ":memory:"
ActiveRecord::Base.connection.instance_eval do
create_table(:articles) { |t| t.string :name }
end
class Article < ActiveRecord::Base; end
a = Article.create name: "foo"
a = Article.find a.id
N = 10
result = ObjectSpace::AllocationTracer.trace do
N.times { Article.find a.id }
end
result.sort.each do |k,v|
p k => v
end
puts "total: #{result.values.map(&:first).inject(:+)}"
```
When I run this against master and this branch I get this output:
```
pete@balloon:~/projects/rails/activerecord$ git checkout master
M Gemfile
Switched to branch 'master'
pete@balloon:~/projects/rails/activerecord$ bundle exec ruby benchmark_allocation_with_callback_send.rb > allocations_before
pete@balloon:~/projects/rails/activerecord$ git checkout remove-dynamic-send-on-built-in-callbacks
M Gemfile
Switched to branch 'remove-dynamic-send-on-built-in-callbacks'
pete@balloon:~/projects/rails/activerecord$ bundle exec ruby benchmark_allocation_with_callback_send.rb > allocations_after
pete@balloon:~/projects/rails/activerecord$ diff allocations_before allocations_after
39d38
<
{["/home/pete/projects/rails/activesupport/lib/active_support/callbacks.rb",
81]=>[40, 0, 0, 0, 0, 0]}
42c41
< total: 630
---
> total: 590
```
In addition to this, there are two micro-optimizations present:
* Using `block.call if block` vs `yield if block_given?` when the block was being captured already.
```
pete@balloon:~/projects$ cat benchmark_block_call_vs_yield.rb
require 'benchmark/ips'
def block_capture_with_yield &block
yield if block_given?
end
def block_capture_with_call &block
block.call if block
end
def no_block_capture
yield if block_given?
end
Benchmark.ips do |b|
b.report("block_capture_with_yield") { block_capture_with_yield }
b.report("block_capture_with_call") { block_capture_with_call }
b.report("no_block_capture") { no_block_capture }
end
pete@balloon:~/projects$ ruby benchmark_block_call_vs_yield.rb
Calculating -------------------------------------
block_capture_with_yield
124979 i/100ms
block_capture_with_call
138340 i/100ms
no_block_capture 136827 i/100ms
-------------------------------------------------
block_capture_with_yield
5703108.9 (±2.4%) i/s - 28495212 in 4.999368s
block_capture_with_call
6840730.5 (±3.6%) i/s - 34169980 in 5.002649s
no_block_capture 5821141.4 (±2.8%) i/s - 29144151 in 5.010580s
```
* Defining and calling methods instead of using send.
```
pete@balloon:~/projects$ cat benchmark_method_call_vs_send.rb
require 'benchmark/ips'
class Foo
def tacos
nil
end
end
my_foo = Foo.new
Benchmark.ips do |b|
b.report('send') { my_foo.send('tacos') }
b.report('call') { my_foo.tacos }
end
pete@balloon:~/projects$ ruby benchmark_method_call_vs_send.rb
Calculating -------------------------------------
send 97736 i/100ms
call 151142 i/100ms
-------------------------------------------------
send 2683730.3 (±2.8%) i/s - 13487568 in 5.029763s
call 8005963.9 (±2.7%) i/s - 40052630 in 5.006604s
```
The result of this is making typical ActiveRecord operations slightly faster:
https://gist.github.com/phiggins/e46e51dcc7edb45b5f98
When we are loading a component and we want to know its version, we are
actually not speaking about the constant but the library itself.
[ci skip]
[Godfrey Chan & Xavier Noria]
The functionality has not changed, but the code is more elegant by
using `reduce` instead of `each`.
This way no accumulator needs to be declared, no explicit return is
needed.
If the request parameters are passed to create_with and where they can
be used to do mass assignment when used in combination with
Relation#create.
Fixes CVE-2014-3514
Conflicts:
activerecord/lib/active_record/relation/query_methods.rb
The documentation on `:on` for validations was inconsistent, and most
only referenced the `:create` and `:update` contexts. I fixed those to
be consistent with the documentation on `AM::Validations.validates`,
which seemed to have the best docs.
[ci skip]
This change prevents a certain class of user error which results when
mistakenly using the `validate` class method instead of the `validates`
class method.
Only apply when all arguments are symbols, because some validations use
the `validate` method and pass in additional options, namely the
`LenghValidator` via the `ActiveMode::Validations::validates_with`
method.
These methods may cause confusion with the `reset_changes` that
behaves differently
of them.
Also rename undo_changes to restore_changes to match this new set of
methods.
This method name is causing confusion with the `reset_#{attribute}`
methods. While `reset_name` set the value of the name attribute for the
previous value the `reset_changes` only discard the changes and previous
changes.
Leave the note for `ActiveModel`, since it can't yet detect mutations
(hopefully we can change this in time for 4.2). However, we now detect
mutations on all supported types in `ActiveRecord`, so we can note that
`_will_change!` is no longer needed there.
It's unintuitive to call '#valid?' when you want to run validations but
don't care about the return value.
The alias in ActiveRecord isn't strictly necessary (the ActiveModel
alias is still in effect), but it clarifies.
This method return `Gem::Version.new(Rails.version)`, suggesting a more
reliable way to perform version comparison.
Example:
Rails.version #=> "4.1.2"
Rails.gem_version #=> #<Gem::Version "4.1.2">
Rails.version > "4.1.10" #=> false
Rails.gem_version > Gem::Version.new("4.1.10") #=> true
Gem::Requirement.new("~> 4.1.2") =~ Rails.gem_version #=> true
This was originally introduced as `.version` by @charliesome in #8501
but got reverted in #10002 since it was not backward compatible.
Also, updating template for `rake update_versions`.
Now that Validator #setup is called from the initializer, we need a
reference to the model's class to be passed in to allow the validators
to continue functioning when used at the instance level.
Closes#14134.
The documentation of `#to_key` (http://api.rubyonrails.org/classes/ActiveModel/Conversion.html#method-i-to_key)
states that it returns `nil` if there are no key attributes. `to_param` needs
to be aware of that fact and return `nil` as well.
Previously it raised the following exception:
```
1) Error:
ConversionTest#test_to_param_returns_nil_if_to_key_is_nil:
NoMethodError: undefined method `join' for nil:NilClass
/Users/senny/Projects/rails/activemodel/lib/active_model/conversion.rb:65:in `to_param'
/Users/senny/Projects/rails/activemodel/test/cases/conversion_test.rb:34:in `block in <class:ConversionTest>'
```
Before this patch after the changes are applied the changes can be only
accessed using string keys, but before symbols are also accepted.
After this change every state of the model will be consistent.
These methods were made "public" in 47617ecd so that `method_missing`
can invoke them without going through `send`, but they aren't meant
for consumption from outside of Rails.