westonganger / protected_attributes_continued

The community continued version of protected_attributes for Rails 5+
MIT License
45 stars 33 forks source link

Do not connect to database if in Rails initialization stage #22

Closed mehagar closed 3 years ago

mehagar commented 3 years ago

In Rails 5.2, a new behavior described here will cause Rails configs to be loaded for all rake tasks.

In our case, if the database has not yet been created (before rake db:create) has been run, and then we run rake db:create, this is what happens:

we are using the audited gem and using its initializer to configure the class to use for auditing:

Audited.config do |config|
  config.audit_class = ::Audit
end

So that class is loaded, which has this code:

class Audit
  attr_protected :customer_id
end

The attr_protected call connects to the database to determine the primary key of the model to use as a default id. But since the database has not yet been created, it fails. This creates a catch-22 where we cannot create the database, because the rake task to create it fails because there is no database.

Ideally there would be a way to opt out of the behavior that by default connects to the database to determine the primary key.

westonganger commented 3 years ago

Which line of protected_attributes_continued accesses the database in your error stacktrace?

You can also use:

Audited.config do |config|
  if Audit.table_exists? 
    config.audit_class = ::Audit
  end
end

###

class Audit
  if Audit.table_exists?
    attr_protected :customer_id
  end
end
mehagar commented 3 years ago

Here is the stack trace:

/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/mysql2-0.5.3/lib/mysql2/client.rb:90:in `connect'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/mysql2-0.5.3/lib/mysql2/client.rb:90:in `initialize'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/mysql2_adapter.rb:22:in `new'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/mysql2_adapter.rb:22:in `mysql2_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:830:in `new_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:874:in `checkout_new_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `try_to_checkout_new_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:814:in `acquire_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:538:in `checkout'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:382:in `connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:1033:in `retrieve_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_handling.rb:90:in `connection'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/model_schema.rb:324:in `table_exists?'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/attribute_methods/primary_key.rb:99:in `get_primary_key'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/attribute_methods/primary_key.rb:87:in `reset_primary_key'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/attribute_methods/primary_key.rb:75:in `primary_key'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/attribute_methods/primary_key.rb:89:in `reset_primary_key'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/attribute_methods/primary_key.rb:75:in `primary_key'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_record/mass_assignment_security/attribute_assignment.rb:15:in `attributes_protected_by_default'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_model/mass_assignment_security.rb:333:in `block in protected_attributes_configs'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_model/mass_assignment_security.rb:222:in `protected_attributes'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_model/mass_assignment_security.rb:126:in `block in attr_protected'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_model/mass_assignment_security.rb:125:in `each'
/Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/protected_attributes_continued-1.7.0/lib/active_model/mass_assignment_security.rb:125:in `attr_protected'
westonganger commented 3 years ago

Does the following monkey patch work for you?

ActiveRecord::MassAssignmentSecurity::ClassMethods.module_eval do

  def attributes_protected_by_default
    if table_exists?
      default = [ primary_key, inheritance_column ]
      default << 'id' unless primary_key.eql? 'id'
      default
    else
      []
    end
  end

end
mehagar commented 3 years ago

It does not, because the call to table_exists? itself tries to connect to the database (shown in this stack trace):

from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/mysql2_adapter.rb:12:in `mysql2_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:830:in `new_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:874:in `checkout_new_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:853:in `try_to_checkout_new_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:814:in `acquire_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:538:in `checkout'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:382:in `connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_adapters/abstract/connection_pool.rb:1033:in `retrieve_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_handling.rb:118:in `retrieve_connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/connection_handling.rb:90:in `connection'
    from /Users/michaelhagar/.rvm/gems/ruby-2.7.2@sa/gems/activerecord-5.2.4.4/lib/active_record/model_schema.rb:324:in `table_exists?'
westonganger commented 3 years ago

How about the following

ActiveRecord::MassAssignmentSecurity::ClassMethods.module_eval do

  def attributes_protected_by_default
    begin
      default = [ primary_key, inheritance_column ]
      default << 'id' unless primary_key.eql? 'id'
      default
    rescue ActiveRecord::NoDatabaseError
      []
    end
  end

end
mehagar commented 3 years ago

Yes, that works.

westonganger commented 3 years ago

Ok this should now be fixed in master.

westonganger commented 3 years ago

v1.8.0 is now released which contains this fix.