Require `belongs_to` by default. #18937

Merged
merged 1 commit into from Feb 23, 2015

Projects

None yet

5 participants

@simi
Contributor
simi commented Feb 14, 2015

Deprecate required option in favor of optional for belongs_to.

closes #18233

@simi
Contributor
simi commented Feb 14, 2015
@simi
Contributor
simi commented Feb 14, 2015

I wonder what to do with failing test.

rails generate scaffold LineItems product:references cart:belongs_to
bundle exec rake db:migrate test`

This code fails now, since belongs_to relation is required by default. Should I change generated test for belongs_to to make it pass or should I remove this test since it is not valid with this change?

@simi simi and 1 other commented on an outdated diff Feb 15, 2015
.../lib/active_record/associations/builder/belongs_to.rb
@@ -110,5 +110,24 @@ def self.add_destroy_callbacks(model, reflection)
name = reflection.name
model.after_destroy lambda { |o| o.association(name).handle_dependency }
end
+
+ def self.define_validations(model, reflection)
+ if reflection.options.key?(:required)
+ ActiveSupport::Deprecation.warn('`required` option for belongs_to relation is deprecated and replaced with `optional` option.')
@simi
simi Feb 15, 2015 Contributor

Better info will be needed here. Any ideas?

@dhh
dhh Feb 15, 2015 Member

'required: true is now the default for belongs_to and can be removed.' – don't even need to talk about optional here. What they wanted to do is now being done for them.

@simi
simi Feb 15, 2015 Contributor

What about when required: false is specified explicitly? Maybe throw deprecation warning only for false value?

@dhh
dhh Feb 15, 2015 Member

That was the old default, which means no client code will have that code, since it doesn’t make any sense.

On Feb 15, 2015, at 12:54 PM, Josef Šimánek notifications@github.com wrote:

In activerecord/lib/active_record/associations/builder/belongs_to.rb #18937 (comment):

@@ -110,5 +110,24 @@ def self.add_destroy_callbacks(model, reflection)
name = reflection.name
model.after_destroy lambda { |o| o.association(name).handle_dependency }
end
+

  • def self.define_validations(model, reflection)
  •  if reflection.options.key?(:required)
    
  •    ActiveSupport::Deprecation.warn('`required` option for belongs_to relation is deprecated and replaced with `optional` option.')
    
    What about when required: false is specified explicitly? Maybe throw deprecation warning only for false value?


Reply to this email directly or view it on GitHub https://github.com/rails/rails/pull/18937/files#r24725940.

@simi
simi Feb 15, 2015 Contributor

OK, makes sense.

@dhh
Member
dhh commented Feb 15, 2015

You can kill that test and replace it with a test that tests optional: true.

On Feb 14, 2015, at 3:24 AM, Josef Šimánek notifications@github.com wrote:

I wonder what to do with failing test.

rails generate scaffold LineItems product:references cart:belongs_to
bundle exec rake db:migrate test`
This code fails now, since belongs_to relation is required by default. Should I change generated test for belongs_to to make it pass or should I remove this test since it is not valid with this change?


Reply to this email directly or view it on GitHub #18937 (comment).

@simi
Contributor
simi commented Feb 18, 2015

Should I add also this to generator? rails g scaffold post author:belongs_to:optional ?

@dhh
Member
dhh commented Feb 18, 2015

Nah, don’t think that’s necessary. Will be a very uncommon option to use, I think.

On Feb 18, 2015, at 14:30, Josef Šimánek notifications@github.com wrote:

Should I add also this to generator? rails g scaffold post author:belongs_to:optional ?


Reply to this email directly or view it on GitHub.

@simi
Contributor
simi commented Feb 18, 2015

OK I think my work is almost done here. I'll add also CHANGELOG note soon.

@dhh
Member
dhh commented Feb 18, 2015

Awesome. Ping when ready to merge.

On Feb 18, 2015, at 15:35, Josef Šimánek notifications@github.com wrote:

OK I think my work is almost done here. I'll add also CHANGELOG note soon.


Reply to this email directly or view it on GitHub.

@simi
Contributor
simi commented Feb 18, 2015

I added CHANGELOG note, but it is a little fuzzy to me. Feel free to ping me with better one.

@dhh dhh and 1 other commented on an outdated diff Feb 18, 2015
activerecord/CHANGELOG.md
@@ -1,3 +1,9 @@
+* Require `belongs_to` association by default.
+ This can by configure by `config.active_record.require_belongs_to` option
+ or by `optional: true` option on relation.
+
+ *Josef Šimánek*
@dhh
dhh Feb 18, 2015 Member

Let's try with:

belongs_to will now trigger a validation error by default if the association is not present. You can turn this off on a per-association basis with optional: true. (Note this new default only applies to new Rails apps that will be generated with config.active_record.require_belongs_to = true)

@simi
simi Feb 18, 2015 Contributor

Actually the final note is not true right now. config.active_record.require_belongs_to = true is set directly in railtie, not in application config. Should I move it to new generated application.rb and let it nil for existing applications? In that case your note will be true.

@dhh
Member
dhh commented Feb 18, 2015

Let's update the config option to be a bit clearer: belongs_to_required_by_default = true

@dhh
Member
dhh commented Feb 19, 2015

This is also missing the config/initializers/active_record_belongs_to_required_by_default.rb file that's going to set the belongs_to_required_by_default = true setting by default for new apps.

@simi
Contributor
simi commented Feb 19, 2015
@dhh
Member
dhh commented Feb 19, 2015

Ah, we crossed streams. Didn't note that one. Yes, please do go ahead. Thanks!

@jeremy jeremy and 1 other commented on an outdated diff Feb 19, 2015
.../lib/active_record/associations/builder/belongs_to.rb
@@ -110,5 +110,23 @@ def self.add_destroy_callbacks(model, reflection)
name = reflection.name
model.after_destroy lambda { |o| o.association(name).handle_dependency }
end
+
+ def self.define_validations(model, reflection)
+ if reflection.options.key?(:required)
+ reflection.options[:optional] = !reflection.options.delete(:required)
+ end
+
+ if reflection.options[:optional].nil?
+ required = model.require_belongs_to
+ else
+ required = !reflection.options[:optional]
+ end
@jeremy
jeremy Feb 19, 2015 Member

I'm finding the control flow here a little difficult to follow. Consider condensing the conditionals and using the :optional option to determine whether to declare the presence validation:

# Disallow conflicting options
if reflection.options.key?(:required) && reflection.options.key?(:optional)
  raise ArgumentError, "complain if we have conflicting options"
# Deprecate :required
elsif reflection.options.key?(:required)
  reflection.options[:optional] = !reflection.options[:required]
  ActiveSupport::Deprecation.warn "switch to `optional: #{reflection.options[:optional]}`"
# Set default :optional option
elsif !reflection.options.key?(:optional)
  reflection.options[:optional] = !model.require_belongs_to
end

super

if !reflection.options[:optional]
  model.validates_presence_of …
end
@simi
simi Feb 19, 2015 Contributor

@jeremy I try to rethink this flow, but deprecation was marked as unnecessary by @dhh here.

@jeremy jeremy commented on the diff Feb 19, 2015
...ord/lib/active_record/associations/builder/has_one.rb
@@ -17,5 +17,12 @@ def self.valid_dependent_options
def self.add_destroy_callbacks(model, reflection)
super unless reflection.options[:through]
end
+
+ def self.define_validations(model, reflection)
+ super
+ if reflection.options[:required]
+ model.validates_presence_of reflection.name, message: :required
+ end
+ end
@jeremy
jeremy Feb 19, 2015 Member

I think it'll be odd to switch to optional: on belongs_to but keep required: for has_one.

@dhh
dhh Feb 19, 2015 Member

I don't think so. belongs_to to me strongly implies a owner/ownee relationship. Doesn't make sense in most cases to say I'm an ownee without a owner. But the other case does make sense. That the owner may or may not have an ownee. (Excuse the made-up word).

@jeremy jeremy and 1 other commented on an outdated diff Feb 19, 2015
activerecord/lib/active_record/core.rb
@@ -87,6 +87,8 @@ def self.configurations
mattr_accessor :maintain_test_schema, instance_accessor: false
+ mattr_accessor :require_belongs_to, instance_accessor: false
+
@jeremy
jeremy Feb 19, 2015 Member

Out of context, I wouldn't know what this "require belongs to" means. Is there a clearer config name?

@simi
simi Feb 19, 2015 Contributor

yup, it is on the way

@jeremy jeremy commented on an outdated diff Feb 19, 2015
activerecord/test/cases/associations/required_test.rb
@@ -34,7 +34,7 @@ class Child < ActiveRecord::Base
test "required belongs_to associations have presence validated" do
model = subclass_of(Child) do
- belongs_to :parent, required: true, inverse_of: false,
+ belongs_to :parent, optional: false, inverse_of: false,
@jeremy
jeremy Feb 19, 2015 Member

If we still support required: true, we should still test it.

@jeremy jeremy and 1 other commented on an outdated diff Feb 19, 2015
activerecord/lib/active_record/railtie.rb
@@ -32,6 +32,7 @@ class Railtie < Rails::Railtie # :nodoc:
config.active_record.use_schema_cache_dump = true
config.active_record.maintain_test_schema = true
+ config.active_record.require_belongs_to = true
@jeremy
jeremy Feb 19, 2015 Member

Since this is in the Railtie, it'll surprisingly affect apps that upgrade to newer Rails. We'll need to generate new apps with it set, but use false as the default to preserve existing app behavior.

@simi
simi Feb 19, 2015 Contributor

sure, it is on the way also

@jeremy
Member
jeremy commented Feb 19, 2015

Nice work @simi. Pardon the repeated remarks, looks like I missed concurrent feedback too. Thanks!

@simi
Contributor
simi commented Feb 19, 2015

Nevermind @jeremy. Thanks for review.

I have just one more question. I made change where config.active_record.require_belongs_to = true is generated to application.rb for new apps. But it is only one uncommented (unless :skip_active_record) configuration in that file. Should I left it there or should it be better to move into custom initializer?

@simi
Contributor
simi commented Feb 19, 2015

I bet it will be better as initializer after inspecting currently generated initializers.

@simi
Contributor
simi commented Feb 19, 2015

@jeremy @dhh I pushed new commit (I can squash later, just to highlight new code). config.active_record.belongs_to_required_by_default = true is now part of config/initializers/active_record_belongs_to_required_by_default.rb.

This initializer is generated only when ActiveRecord is not skipped and is not added for rake rails:update. I think this is compatible enough to skip deprecation.

@dhh
Member
dhh commented Feb 19, 2015

I think we want the deprecation still in place because we want people to switch over. That is, set config.active_record.belongs_to_required_by_default themselves, and then update with optional or nothing as needed. Because we'll remove config.active_record.belongs_to_required_by_default in Rails 5.x or 6.

@simi
Contributor
simi commented Feb 19, 2015

Ahh, ok. Probably I misunderstood you in comments. I'll add deprecation back.

@simi
Contributor
simi commented Feb 19, 2015

I mean here - #18937 (diff).

@dhh
Member
dhh commented Feb 19, 2015

Hmm, actually. Good point. Not sure there is a good way to inform about this via a deprecation. I think we’ll have to simply make the deprecation a matter of documentation on the belongs_to method.

On Feb 18, 2015, at 5:57 PM, Josef Šimánek notifications@github.com wrote:

I mean here - #18937 (diff) #18937 (diff).


Reply to this email directly or view it on GitHub #18937 (comment).

@simi
Contributor
simi commented Feb 19, 2015

I'll add this into documentation anyway.

Deprecation can be returned also to https://github.com/rails/rails/pull/18937/files#diff-a26dc1fd56f9dac74db1300aa767378dR24.

@dhh
Member
dhh commented Feb 19, 2015

I think all we need is the documentation updates now.

On Feb 18, 2015, at 6:03 PM, Josef Šimánek notifications@github.com wrote:

I'll add this into documentation anyway.

Deprecation can be returned also to https://github.com/rails/rails/pull/18937/files#diff-a26dc1fd56f9dac74db1300aa767378dR24 https://github.com/rails/rails/pull/18937/files#diff-a26dc1fd56f9dac74db1300aa767378dR24.


Reply to this email directly or view it on GitHub #18937 (comment).

@simi simi added a commit to simi/rails that referenced this pull request Feb 19, 2015
@simi simi Add basic documentation for #18937.
[ci skip]
42f1803
@simi simi added a commit to simi/rails that referenced this pull request Feb 19, 2015
@simi simi Guides fix for #18937.
[ci skip]
77e193b
@simi
Contributor
simi commented Feb 19, 2015

I added some really basic documentation note:

screenshot-localhost 3333 2015-02-19 03-17-43

I did a quick read thru guides and I changed validated documentation to optional for belongs_to there.

@dhh
Member
dhh commented Feb 19, 2015

Looks good. We can add further refinements later!

On Feb 18, 2015, at 6:31 PM, Josef Šimánek notifications@github.com wrote:

I added some really basic documentation note.

https://cloud.githubusercontent.com/assets/193936/6260922/dde41da0-b7e6-11e4-915c-9eba00c1ddde.png
I did a quick read thru guides and I changed validated documentation to optional for belongs_to there.


Reply to this email directly or view it on GitHub #18937 (comment).

@simi
Contributor
simi commented Feb 19, 2015
@rafaelfranca rafaelfranca commented on an outdated diff Feb 20, 2015
guides/source/association_basics.md
@@ -832,7 +832,7 @@ The `belongs_to` association supports these options:
* `:inverse_of`
* `:polymorphic`
* `:touch`
-* `:validate`
+* `:optional`
@rafaelfranca
rafaelfranca Feb 20, 2015 Member

We still have the :validate option. It is not the same as :required so we should keep its documentation.

@rafaelfranca
Member

We need to document belongs_to_required_by_default in the configuration guide.

Also had you tested the initializer in a new application? I'm not sure if it will work as it is right now because of load order of Active Record.

@simi simi Require `belongs_to` by default.
Deprecate `required` option in favor of `optional` for belongs_to.
6576f73
@simi
Contributor
simi commented Feb 21, 2015

@rafaelfranca I reverted my validate doc changes and added a note into configuration guide.

@simi
Contributor
simi commented Feb 21, 2015

And I tested this via:

bin/rails new artest --dev
cd artest
bin/rails g model post
bin/rails g model comment post:belongs_to
bin/rake db:migrate
bin/rails runner "p Comment.new.valid?" #=> false
RAILS_ENV=production bin/rake db:migrate
RAILS_ENV=production bin/rails runner "p Comment.new.valid?" #=> false
@rafaelfranca rafaelfranca merged commit 069c03b into rails:master Feb 23, 2015

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@ashishtajane ashishtajane added a commit to ashishtajane/doorkeeper that referenced this pull request Jan 27, 2016
@ashishtajane ashishtajane Make application relation optional
This PR (rails/rails#18937) makes all
`belongs_to` relations required by default. So make application
relation optional manually as it is not required.

[Fixes #774]
2021b14
@mockdeep

Just came across this pull request. I think validating presence of belongs_to relationships is going to result in a lot of unnecessary additional queries in codebases, especially if you're validating a number of records before bulk importing or updating them in the database. We've been moving in the direction of instead validating presence of the associated _id and letting the foreign key constraint in the database ensure the associated record exists.

@perceptec perceptec added a commit to perceptec/rails that referenced this pull request Mar 4, 2016
@perceptec perceptec Fix author callback in engines guide [ci skip]
The `before_save` callback used with `set_author` results in the
validation error "Author must exist," due to the change in `belongs_to`
behavior introduced by #18937.

Use `before_validation` instead.
3de9322
@frank-west-iii frank-west-iii added a commit to q-centrix/health-data-standards that referenced this pull request Jan 5, 2017
@frank-west-iii frank-west-iii Changes the belongs_to relationships to optional
The referenced change for rails 5 is making the belongs_to relationships
break validations for our models. This change makes them optional.

rails/rails#18937
3e2a8da
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment