rails / rails

Ruby on Rails
https://rubyonrails.org
MIT License
55.98k stars 21.65k forks source link

ActiveStorage 'has_one_attached', 'has_many_attached' NoMethodError #32933

Closed ravishwetha closed 6 years ago

ravishwetha commented 6 years ago

Steps to reproduce

  1. Updated rails gem from 5.1.4 to 5.2.0:

    rails (5.2.0)
      actioncable (= 5.2.0)
      actionmailer (= 5.2.0)
      actionpack (= 5.2.0)
      actionview (= 5.2.0)
      activejob (= 5.2.0)
      activemodel (= 5.2.0)
      activerecord (= 5.2.0)
      activestorage (= 5.2.0)
      activesupport (= 5.2.0)
      bundler (>= 1.3.0)
      railties (= 5.2.0)
      sprockets-rails (>= 2.0.0)
  2. ran rails active_storage:install

  3. ran rails db:migrate: 2 new tables created, active_storage_attachments and active_storage_blobs

    create_table "active_storage_attachments", force: :cascade do |t|
    t.string "name", null: false
    t.string "record_type", null: false
    t.bigint "record_id", null: false
    t.bigint "blob_id", null: false
    t.datetime "created_at", null: false
    t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
    t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
    end
    
    create_table "active_storage_blobs", force: :cascade do |t|
    t.string "key", null: false
    t.string "filename", null: false
    t.string "content_type"
    t.text "metadata"
    t.bigint "byte_size", null: false
    t.string "checksum", null: false
    t.datetime "created_at", null: false
    t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
    end
  4. Created config/storage.yml file:

    
    local:
    service: Disk
    root: <%= Rails.root.join("storage") %>

local_test: service: Disk root: <%= Rails.root.join("tmp/storage") %>


5.
Added config.active_storage.service to config/environments/development.rb

Rails.application.configure do

Tell Active Storage which service to use by setting Rails.application.config.active_storage.service.

Because each environment will likely use a different service, it is recommended to do this on a per-environment basis.

config.active_storage.service = :local end


6. Added 'has_many_attached :documents' to model.rb file:

class Model < ActiveRecord::Base has_many_attached :documents


7. Tried 'has_one_attached :avatar' to model.rb file:

class Model < ActiveRecord::Base has_one_attached :avatar


### Expected behavior
The server should start (with rails s) or the console should start (with rails c)

### Actual behavior
has_many_attached:
/Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/dynamic_matchers.rb:22:in `method_missing': undefined method `has_many_attached' for Model (call 'Model.connection' to establish a connection):Class (NoMethodError)

has_one_attached:
/Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/dynamic_matchers.rb:22:in `method_missing': undefined method `has_one_attached' for Model (call 'Model.connection' to establish a connection):Class (NoMethodError)

for both 'rails s' and 'rails c' commands mentioned above
has_one_attached :avatar gives the same erorr

### System configuration
**Rails version**: 5.2.0
**Ruby version**: 2.5.1

### Attempts to resolve the issue
Reinstalling all gems including ruby and rails

### Full trace

Traceback (most recent call last): 84: from bin/rails:3:in <main>' 83: from bin/rails:3:inload' 82: from /Users/username/Documents/repository-name/bin/spring:15:in <top (required)>' 81: from /Users/username/Documents/repository-name/bin/spring:15:inrequire' 80: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/binstub.rb:31:in <top (required)>' 79: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/binstub.rb:31:inload' 78: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/bin/spring:49:in <top (required)>' 77: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/client.rb:30:inrun' 76: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/client/command.rb:7:in call' 75: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/client/rails.rb:28:incall' 74: from /Users/username/.rvm/gems/ruby-2.5.1/gems/spring-2.0.2/lib/spring/client/rails.rb:28:in load' 73: from /Users/username/Documents/repository-name/bin/rails:9:in<top (required)>' 72: from /Users/username/Documents/repository-name/bin/rails:9:in require' 71: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands.rb:18:in<top (required)>' 70: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/command.rb:46:in invoke' 69: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/command/base.rb:65:inperform' 68: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor.rb:387:in dispatch' 67: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/invocation.rb:126:ininvoke_command' 66: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/thor-0.20.0/lib/thor/command.rb:27:in run' 65: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:142:inperform' 64: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:142:in tap' 63: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:147:inblock in perform' 62: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:51:in start' 61: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:89:inlog_to_stdout' 60: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/server.rb:354:in wrapped_app' 59: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/commands/server/server_command.rb:27:inapp' 58: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/server.rb:219:in app' 57: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/server.rb:319:inbuild_app_and_options_from_config' 56: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/builder.rb:40:in parse_file' 55: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/builder.rb:49:innew_from_string' 54: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/builder.rb:49:in eval' 53: from config.ru:in

' 52: from config.ru:in new' 51: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/builder.rb:55:ininitialize' 50: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/rack-2.0.5/lib/rack/builder.rb:55:in instance_eval' 49: from config.ru:3:inblock in
' 48: from config.ru:3:in require_relative' 47: from /Users/username/Documents/repository-name/config/environment.rb:5:in<top (required)>' 46: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/application.rb:361:in initialize!' 45: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:60:inrun_initializers' 44: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:205:in tsort_each' 43: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:226:intsort_each' 42: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:347:in each_strongly_connected_component' 41: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:347:incall' 40: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:347:in each' 39: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:349:inblock in each_strongly_connected_component' 38: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:415:in each_strongly_connected_component_from' 37: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:415:incall' 36: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:50:in tsort_each_child' 35: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:50:ineach' 34: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:421:in block in each_strongly_connected_component_from' 33: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:431:ineach_strongly_connected_component_from' 32: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:422:in block (2 levels) in each_strongly_connected_component_from' 31: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:350:inblock (2 levels) in each_strongly_connected_component' 30: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/2.5.0/tsort.rb:228:in block in tsort_each' 29: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:61:inblock in run_initializers' 28: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:in run' 27: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/initializable.rb:32:ininstance_exec' 26: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:613:in block in <class:Engine>' 25: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:613:ineach' 24: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:614:in block (2 levels) in <class:Engine>' 23: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:656:inload_config_initializer' 22: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications.rb:170:in instrument' 21: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:657:inblock in load_config_initializer' 20: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:277:in load' 19: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:249:inload_dependency' 18: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:277:in block in load' 17: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:277:inload' 16: from /Users/username/Documents/repository-name/config/initializers/constants.rb:4:in <top (required)>' 15: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:193:inconst_missing' 14: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:502:in load_missing_constant' 13: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:348:inrequire_or_load' 12: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:37:in load_interlock' 11: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies/interlock.rb:13:inloading' 10: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/concurrency/share_lock.rb:151:in exclusive' 9: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies/interlock.rb:14:inblock in loading' 8: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:37:in block in load_interlock' 7: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:365:inblock in require_or_load' 6: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:467:in load_file' 5: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:653:innew_constants_in' 4: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:468:in block in load_file' 3: from /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activesupport-5.2.0/lib/active_support/dependencies.rb:468:inload' 2: from /Users/username/Documents/repository-name/app/models/model.rb:1:in <top (required)>' 1: from /Users/username/Documents/repository-name/app/models/model.rb:2:in' /Users/username/.rvm/rubies/ruby-2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/dynamic_matchers.rb:22:in method_missing': undefined methodhas_one_attached' for Model (call 'Model.connection' to establish a connection):Class (NoMethodError)

y-yagi commented 6 years ago

I can't reproduce this with your steps.
If you created an application in the same state, will it reproduce in your environment?

jacobsmith commented 6 years ago

@ravishwetha I recently saw something similar in one of my repos. It was an API only repo (not sure if that matters or not).

I ended up having to add:

require "active_storage/engine" to the config/application.rb file.

I believe this is because I upgraded a repo that did not have active storage, so it was not being auto-required properly on startup.

ravishwetha commented 6 years ago

@y-yagi Haven't tried this, I am not exactly sure what you mean by 'same state'. Do you mean a new application with the same Gemfile and a Model using the "has_many_attached :documents" to see it if it works?

@jacobsmith It is not API only, I tried require "active_storage/engine" in the file but I get the same error: undefined method `has_one_attached' for Model. Yes I just upgraded an app in the hope of using ActiveStorage (it was not beforehand).

y-yagi commented 6 years ago

Do you mean a new application with the same Gemfile and a Model using the "has_many_attached :documents" to see it if it works?

Yes. I tried to reproduce this, but this issue could not be reproduced. Can you please provide a sample application that reproduces the issue?

scarroll32 commented 6 years ago

I'm having the same issue, but adding require "active_storage/engine" has not helped. Will continue investigating.

Branch with the error is here: https://github.com/crowdAI/crowdai/commit/83e07ea3c04816718b5cafa961320a6b8428c6ed

Update

Have found the trigger ... will report back soon.

bhanubhakta commented 6 years ago

@seanfcarroll I you don't need to mention gem 'activestorage' in the gemfile. Could you try with removing that.

scarroll32 commented 6 years ago

ah yes, I have found there is a conflict with some other metaprogramming code we are using

gem 'rails-gdpr-export'

I should have some time to properly analyse it tomorrow or Thursday. Will post back here.

benwalsh commented 6 years ago

On tenterhooks here! Have exactly the same error. Not using that particular gem but perhaps it's a dependency.

To follow your lead, I just created a new Rails app and a single model file with a has_one_attached call in it; added the GDPR gem and it still worked, did not throw the undefined method error.

benwalsh commented 6 years ago

OK, I figured out what was causing the issue in my case. It was a flavour of circular dependency. Reproduce thusly:

# config/initializers/thing.rb
GLOBAL_THING = { thing: Thing::THING }.freeze
# app/models/thing.rb
class Thing < ActiveRecord::Base

THING = 'Thing'.freeze

has_one_attached :thingie

Removing the constant declaration from the initializer file and putting it into the relevant model fixed the problem.

scarroll32 commented 6 years ago

Spent about half a day on this and in the end have decided to take another approach to solve my GDPR download requirements.

The GDPR export gem and ActiveStorage are somehow conflicting. The symptoms were that if a model was annotated by the GDPR gem, the has_one_attached method was not available to the model.

I couldn't see anything in the GDPR export gem that looks like it should conflict with core Rails, so I think there is still could be an undiagnosed bug in ActiveStorage.

CC @grischoun

y-yagi commented 6 years ago

@seanfcarroll I tried has_one_attached with GDPR export gem, but it seems work correctly. https://github.com/y-yagi/reproduce_32933

Can you please provide a sample application that reproduces the issue?

lethan commented 6 years ago

I have made a sample application the reproduces the issue. It uses the clearance gem. https://github.com/lethan/clearance_active_storage

tpett commented 6 years ago

I was seeing this issue as well. By looking at the trace I found I was referencing the model with the has_one_attached call in an initializer. When the constant autoloader kicked in and tried to load that class the ActiveStorage macros were not available causing the NoMethodError to bubble up. I was able to patch it in my case by removing the direct reference to the constant in my initializer. For some reason it looks like ActiveStorage is not fully set up when the initializers are run.

I'm not sure my issue is the exact issue happening here, but hopefully it will help in digging to the bottom of it.

y-yagi commented 6 years ago

@lethan Thanks for the sample application. I can reproduce.

@tpett Thanks for the detailed explanation. You are right. In Active Storage, define methods such as has_one_attached when loading Active Record is executed. https://github.com/rails/rails/blob/5dc72378b783e924c5bf079ca660388ec4ac9224/activestorage/lib/active_storage/engine.rb#L60..L66

If a model is a trigger for loading Active Record(e.g: by referring directly the model in initialize), model will be loaded before the above hook, resulting in raise ArgumentError. If possible, avoiding referencing model directly with the initializer is a temporary workaround.

rafaelfranca commented 6 years ago

There are two problem on loading Active Record models in initializers, one of them is that it will mess with the code reload, those models may not be able to be reloaded anymore.

Another one is that it will make active record load early, and that will not only causes problems like this one in this issue, but also other problems with many other gems and even with active record configurations.

That being said, the correct fix is to not load models in initializers. The application is not initialized yet, so there is no guarantee it will work.

cedm commented 5 years ago

Sometimes models have to be loaded in initializers, for instance when using the Apartment gem with excluded models. Fortunately, there's a solution:

Given an Account model, with has_one_attached :logo, the workaround is:

Model:

-  has_one_attached :logo
+  has_one :logo_attachment, -> { where(name: 'logo') }, class_name: "ActiveStorage::Attachment", as: :record, inverse_of: :record, dependent: false
+  has_one :logo_blob, through: :logo_attachment, class_name: "ActiveStorage::Blob", source: :blob

Controller:

# In create/update action, before calling @account.save/@account.update:
+  if account_params[:logo].present?
+    blob = ActiveStorage::Blob.build_after_upload(
+      io: account_params[:logo],
+      filename: account_params[:logo].original_filename,
+      content_type: account_params[:logo].content_type
+    )
+    @account.logo_blob = blob
+  end

Other methods:

-  @account.logo.attached?
+  @account.logo_attachment.present?

-  rails_blob_path(@account.logo)
+  rails_blob_path(@account.logo_blob)

-  @account.logo.variant(...)
+  @account.logo_blob.variant(...)

-  @account.logo.download
+  @account.logo_blob.download

-  @account.logo.purge
+  @account.logo_attachment.purge

EDIT: exclude :logo parameter when saving/updating record (i.e. @account.update(account_params.except(:logo))), or use a different parameter name.

damienlethiec commented 4 years ago

I there !

I have the same issue with the Shopify App gem. The workaround is tenious. Any other solution?