I have a model BusinessLog:
class BusinessLog < ActiveRecord::Base
include BusinessLogging
default_scope { where(business_id: Business.current.id) }
end
it performs operations in a default scope and includes a concern BusinessLog:
class BusinessLog < ActiveRecord::Base
module BusinessLogging
extend ActiveSupport::Concern
module ClassMethods
def log!(options)
create! business_id: options[:business_id], user_id: options[:user_id], action: options[:action]
end
end
end
end
this log! method was being called by a sidekiq worker. Eventually I discovered I couldn't log for several business because of BusinessLog default scope. So I removed the BusinessLog default scope, but it was somehow stuck.
Whenever I tried to perform operations through the module (eg: BusinessLog.log!(options)), it was still working with a default scope (even thought I already removed it). But when I performed operations in the model itself (eg: BusinessLog.create!(options)), it was working as expected, without the -already gone- default scope.
I tried a lot of things to make it work, but only one thing worked: Renamed the module and everything worked perfectly again.
It's worth saying that this just happened on my production environment.
Has anyone been through this? Should I post it as an issue on github? How could I give more information about this -bug-?
I'm using Rails 4.0.1 and ruby 2.0.0
Related
Consider a Rails 6 application that has app/models/application_record.rb. This Rails 6 application is using Zeitwerk loader.
class ApplicationRecord
end
If I want to add functionality to ApplicationRecord via a module:
# app/models/concerns/fancy_methods.rb
module FancyMethods
def fancy_pants
puts "I'm wearing fancy pants"
end
end
and do the following:
class ApplicationRecord
include FancyMethods
end
I will get a deprecation warning or error:
DEPRECATION WARNING: Initialization autoloaded the constant FancyMethods.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload FancyMethods, for example,
the expected changes wont be reflected in that stale Module object.
This autoloaded constant has been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
(called from <top (required)> at /Users/peter/work/recognize/config/environment.rb:5)
I've read lots of articles including the Rails autoload docs, but nothing really addresses this minimal but common case of extending ApplicationRecord. Yes, I could wrap ApplicationRecord in a .to_prepare block like:
Rails.configuration.to_prepare do
class ApplicationRecord < ActiveRecord::Base
include FancyMethods
end
end
But this seems like a code smell and could cause other unexpected problems now or down the line.
Figured it out! The issue was there was an explicit require in an initializer that loaded ApplicationRecord.
# config/initializers/setup_other_fancy_thing.rb
require 'application_record'
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
ApplicationRecord.send(:include, OtherFancyThing)
The way I debugged this was that I:
Created a new Rails app of the same version and could not reproduce the error
I copied the default application.rb and development.rb and still got the error
Moved the entire config/initializers directory to a temp directory and it made the warning go away! So, I knew it had to be one of the initializers. From there it was just a matter of dividing and conquering until I found the offending initializer.
Great! So, basically, you do not need to define the module and include it that way. Idiomatically, you define the module in its own file, normal:
# app/models/other_fancy_thing.rb
module OtherFancyThing
def also_fancy
puts 'also fancy'
end
end
and then
# app/models/application_record.rb
class ApplicationRecord
include FancyMethods
end
Whenever ApplicationRecord is loaded, for example as a side-effect or loading some regular model, it will load FancyMethods just fine.
Note: There are numerous answers explaining that you can get this error when you subclass ActiveRecord::Base and add an #initialize without super. No answer explains what is actually happening.
I am working in someone else's code and I have an HTTParty service in a Rails app with the following class hierarchy. Note the subclass #initialize with a differing signature to the parent class.
module A
class Base
include HTTParty
...
end
end
module A
class User < Base
def initialize(user)
#user = user
end
end
end
module A
class PublicUser < User
def initialize(opts = {})
#limit = opts[:limit]
# no call to super
end
end
end
Locally there are no problems with this, but in SemaphoreCI the following results:
A::PublicUser.new(limit: 1).some_method
undefined method `some_method' for #<A::PublicUser not initialized>
I can't find any documentation about the "not initialized" message. What causes this sort of failure?
OK, I got it. I also tagged your question with ruby-on-rails, since plain good ruby would rare give such a weird behaviour.
You have experienced two different issues, more or less unrelated.
#<A::PublicUser not initialized> is a result of (sic!) calling inspect on A::PublicUser. So, ruby tries to format an error message and—voilà—the class is printed out that way.
Rails messes with you, as well as with constant lookup. A::Base name conflicts with ActiveRecord::Base, and guess what is resolved when class User < Base is met. To replicate this behaviour you might open a console and do: class Q < ActiveRecord::Base; end; Q.allocate, resulting in #<Q not initialized>. (Do you already love Rails as I do?)
To fix this, either explicitly specify class User < A::Base or rename Base to MyBase. Sorry for suggesting that.
Note: Before you think of marking this question as a duplicate of an other similar question, do note this thing that this question is being asked about concerns in Rails, while the other questions that I've searched deal with controllers. No question I've found out, that deals with concern.
I've a file named comments_deletion.rb inside app/models/concerns, and it contains the following code:
module CommentsDeletion
extend ActiveSupport::Concern
included do
after_save :delete_comments, if: :soft_deleted?
end
def soft_deleted?
status == 'deleted'
end
def delete_comments
comments.each &:destroy
end
end
And I'm trying to mix the file in my model by writing the following code:
class Employee < ActiveRecord::Base
include CommentsDeletion
# all the other code
end
Just doing this, and then upon invoking rails console, it gives me the following error:
Circular dependency detected while autoloading constant Concerns::CommentsDeletion
I'm using Rails 4.0.2, and this thing has driven me nuts, and I'm unable to figure out what's wrong with my code.
Very strange that the following thing hasn't been mentioned anywhere in Rails documentation, but with it, my code works without any problems.
All you have to do is to replace CommentsDeletion with Concerns::CommentsDeletion. Put otherwise, you have to put Concerns before the name of your module that you would like to mix into your models later on.
Now, that's how my module residing inside concerns directory looks like:
module Concerns::CommentsDeletion
extend ActiveSupport::Concern
included do
after_save :delete_comments, if: :soft_deleted?
end
def soft_deleted?
status == 'deleted'
end
def delete_comments
comments.each &:destroy
end
end
In my case, my code likes like:
#models/user.rb
class User < ApplicationRecord
include User::AuditLog
end
and
#model/concern/user/audit_log.rb
module User::AuditLog
extend ActiveSupport::Concern
end
it works fine in development environment, but in production it got error as title. When I change to this it works fine for me. Rename the folder name in concern if it has the same name with models.
#models/user.rb
class User < ApplicationRecord
include Users::AuditLog
end
and
#model/concern/users/audit_log.rb
module Users::AuditLog
extend ActiveSupport::Concern
end
Using Rails 3.2.2 and Ruby 1.9.2.
I have a rails mountable engine EngineA that declares a User class inheriting form ActiveRecord::Base. I have another engine EngineB that wants to inject functionality into EngineA::User. Right now what I have done is shown below:
Method 1:
#EngineA app/models/engine_a/user.rb
module EngineA
class User < ActiveRecord::Base
has_attached_file :avatar
has_many :somethings
end
end
#EngineB lib/engine_b/user.rb
module EngineB
module User
def self.extended obj
obj.class_eval do
has_many :something_elses
end
end
end
end
EngineA::User.extend EngineB::User
This gives me an uninitialized constant EngineA::User error. Even when I require that specific file I run into the problem of EngineA needing paperclip so that has_attached_file is understood. That road ended when I realized I would have to know and require the dependencies for EngineA inside EngineB.
Method 2:
I used the same code as before except I removed the last line EngineA::User.extend EngineB::User from the EngineB user.rb file. I then moved that call to an initializer inside EngineB.
#EngineB config/initializers/my_mixin.rb
EngineA::User.extend EngineB::User
This worked perfectly!!! Except in development mode when I would change code and the models would refresh. The only thing that was refreshed was the EngineA::User and not the mixin that I had put as an initializer. So once I changed code, I lost all of my extended functionality.
I'm not even positive this is the most 'efficient' way to do this... any help would be greatly appreciated. Thanks in advance.
According to the configuration documentation, you can use an ActionDispatch callback to load items. These callbacks will run when at every request if cache_classes is set to false, like in development mode.
Inside of your EngineB.rb file, you might try something like this:
if Rails.env.development?
ActionDispatch::Callbacks.to_prepare do
load "#{File.expand_path(File.dirname(__FILE__))}/../config/initializers/my_mixin.rb"
end
end
I've found a way to make this work, but am curious about a better way / the Rails 3 way. (I'm using 2.3.5 still, but hope to migrate around New Year's.)
The situation: I've got two layers of module inheritance, the second layer gets mixed into a Rails model. Both modules define validation methods and I'd like both of them to attach the validations to the base class, but because of the two levels of inheritance, the following doesn't work:
def self.included(base)
base.validate :yadda_yadda
end
When that module is included by another module, the interpreter grinds to a screeching halt because Modules don't know about ActiveRecord::Validations. Including the validations module begs the question of "where is save?" thanks to alias_method.
The following works, as long as you remember to call super whenever you override validate(). I don't trust myself or future maintainers to remember that, so I'd like to use the validate :yadda_yadda idiom instead, if possible.
module Grandpa
def validate
must_be_ok
end
def must_be_ok
errors.add_to_base("#{self} wasn't ok")
end
end
module Dad
include Grandpa
def validate
super
must_be_ok_too
end
def must_be_ok_too
errors.add_to_base("#{self} wasn't ok either")
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
def must_be_ok_three
errors.add_to_base("#{self} wasn't ok furthermore")
end
end
Suggestions? Tips for Rails 3 approach? I don't think the validations API has changed that much.
I solved it (when I ran into the same problem, but with something other than validation).
Short answer: you can call send(:included, base) on the module you want to bring in. Within the higher-up included() definition, you need to check whether the base is a Class or a Module.
Why would you ever want to do this? Well, I've got some modules that extract some common functionality out of my models. For instance, the module HasAllocable sets up a polymorphic belongs_to relationship, and a getter/setter pair for a virtual attribute. Now I have another module that needs to pull in HasAllocable, to spare the base classes from having to remember it.
I'd be interested to know whether this smells funny to anyone. I haven't seen anything like it on the web, so I wonder if multiple layers of model inheritance is more of an antipattern.
module Grandpa
def self.included(base)
if base.kind_of?(Class)
base.validate :must_be_ok
end
end
end
module Dad
include Grandpa
def self.included(base)
if base.kind_of?(Class)
# you can do this
#base.send(:include, Grandpa)
# you can also do this
Grandpa.send(:included, base)
# this does not invoke Grandpa.included(Kid)
#super(base)
base.validate :must_be_ok_too
end
end
end
class Kid < ActiveRecord::Base
include Dad
validate :must_be_ok_three
end