Ripping my hair out on this one:
app/models/concerns/soft_delete.rb:
module SoftDelete
extend ActiveSupport::Concern
module ClassMethods
def testing_a_class
pp "Ima class method"
end
end
def testing_an_instance
pp "Ima instance method"
end
end
class User < ActiveRecord::Base
include SoftDelete
end
app/models/user.rb:
class User < ActiveRecord::Base
testing_a_class
end
Now, in the rails console:
x = User.first # I expect "Ima class method" to be printed to the screen
NameError: undefined local variable or method `testing_a_class' for User(no database connection):Class
I don't know where you saw this idea of including a module in the same file where it's defined, but you must not do it (in rails), because of how rails works (auto-lazy-loading).
Rails doesn't load all classes on startup. Instead when you reference a class that doesn't yet exist, rails attempts to guess where it might be located and loads it from there.
x = User.first
Before this line, constant User does not exist (assuming it wasn't referenced before). When trying to resolve this name, rails will look for file user.rb in each of autoload_paths (google it up). It will find one at app/models/user.rb. Next time you reference User, it will just use this constant and will not look it up in the filesystem.
# app/models/user.rb:
class User < ActiveRecord::Base
testing_a_class
end
The definition that was found contains only an invocation of some unknown method (hence the error). Code in your concern file was not loaded and will never be loaded. To fix this, include the concern in the model file.
# app/models/user.rb:
class User < ActiveRecord::Base
include SoftDelete
testing_a_class
end
Now it should work.
Related
I have a concern that creates a class macro that I want available for all the models in my Rails application. So I'm including it in ApplicationRecord. The code is as follows:
# application_record.rb
class ApplicationRecord < ActiveRecord::Base
include ::TestConcern
end
# app/concerns/test_concern.rb
module TestConcern
extend ActiveSupport::Concern
class_methods do
def some_class_macro_all_models_must_have
User.some_class_instance_variable << self
end
end
included do
User.include(UserModule)
end
module UserModule
def self.included(base)
base.class_eval do
def self.some_class_instance_variable
#some_class_instance_variable ||= Set.new
end
end
end
end
end
As you can see, the class macro will actually interact with a class instance variable in the model User.
So that's why, on the included hook of the concern, I'm trying to class_eval the User model to have that class instance variable initialized. The plan was to do it like this because otherwise any model can be invoking the class macro BEFORE the class instance variable is initialized in the User model.
However, this errors out with Circular dependency detected while autoloading constant User. As far as I can understand, ApplicationRecord loads, it includes the module, the module included hooks is called, it references the User model, and so the User model is loaded, which inherits from ApplicationRecord (which didn't finish loading yet), so it causes the circular dependency.
How to avoid this circular dependency paradox, knowing that many models will invoke this class macro, and those classes might be loaded before the User class itself, so I can't even count on defining the some_class_instance_variable class method in the User model itself?
After giving it some extra thought, I decided to simply store the some_class_instance_variable in the concern itself, and since the model User also called the some_class_macro_all_models_must_have, I decided to include the UserModule when it was invoked, effectively eliminating both the circular dependency and the load order issue.
The real code is much more complex than this contrived example, but the end result was something like this:
module TestConcern
def self.some_class_instance_variable
#some_class_instance_variable ||= Set.new
end
extend ActiveSupport::Concern
class_methods do
def some_class_macro_all_models_must_have
User.include(UserModule) if self == User
TestConcern.some_class_instance_variable << self
end
end
included do
end
module UserModule
def self.included(base)
base.class_eval do
# Class macro invocations, class method and instance method definitions
end
end
end
end
I'm trying to call a global method in a file in the lib/ directory from a model. I've already tried with concerns and the problem persists.
The app is developed in Ruby on Rails 5.2.1 and Ruby 2.5.3
# Expense model
class Expense < ApplicationRecord
include Helpers
def self.quantity_this_month
select { |e| year_month(e.date) == year_month(Date.today)
}.count
end
end
# Helper in lib/ directory
module Helpers
def year_month(date)
date.strftime('%Y/%m')
end
end
# in console
Expense.quantity_this_month
Implementing the code of the helper directly in the model method gives the expected result, but right now it shows this error:
undefined method `year_month' for #<Class:0x00007f2f6f0e4b88>
Any ideas?
You need to extend instead of include because the method you want to call it from is a class method.
Also, just a side note, your self.quantity_this_month loads all the Expense records into memory, but it's possible to do the date filtering and counting all within a single SQL query. The below is how it's done in MySQL, this might be slightly different with other databases:
class Expense < ApplicationRecord
extend Helpers # <~~~~~~~ changed to extend
def self.quantity_this_month
where(
"DATE_FORMAT(date, '%Y/%m') = ?",
year_month(Date.today)
).count
end
end
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
I am doing Rails app and i see that i could refactor my code. Just can't find how. I have many models like Company, News, Profile and many more which use Image upload. So in every class i must always copy-paste 30 rows of methods which implement always the same logic - upload_image, get_image_name, delete_image. How is possible to do, that my Model class would automatically have the methods from somewhere else? I would like to just put to the Model - load 'GlobalMethods', or even somehow include the methds in activerecord:base to just have them for every class and i would use whenever i want. And from controller i would just leave as it is. For example - News.upload_image and it would do this same as this logic would be in original model.
Please explain with example, because i have readed more and didn't understand or it even possible.
At this moment i did:
#models/concerns/uploading.rb
module Uploading
extend ActiveSupport::Concern
included do
def do_upload
puts 'aaaaaaaaaaaaaaa'
end
end
end
and model:
class Company < ActiveRecord::Base
include Uploading
end
and i get this error:
uninitialized constant Company::Uploading
My rails version: '4.2.5'
I did server restarts after every try
My application.rb file looks:
require File.expand_path('../boot', __FILE__)
require 'rails/all'
Bundler.require(*Rails.groups)
module Vca
class Application < Rails::Application
config.active_record.raise_in_transactional_callbacks = true
config.autoload_paths += %W(
#{config.root}/app/models/concerns/uploading.rb
)
end
end
Still the same
NameError in CompaniesController#index
uninitialized constant Company::Uploading
class Company < ActiveRecord::Base
include Uploading # <-- error
end
I have gem 'Spring'. I turned off server, in terminal i runned "spring stop" and started server. But it didn't solve this.
Take a look at ActiveSupport::Concern
For example
app/models/concerns/my_concern.rb
module MyConcern
extend ActiveSupport::Concern
included do
# common stuff here
end
class_methods do
# define class methods here
end
end
app/models/my_model.rb
class MyModel < ActiveRecord::Base
include MyConcern
# ...
end
By the way, you can refer to the model class with self in the included block and out of any instance methods, and inside the methods within the class_methods block.
You'll notice that your standard rails project includes a folder called concerns under your models folder.
Create a module in that folder, image_handling.rb
module ImageHandling
extend ActiveSupport::Concern
included do
# ...(include all your common methods here)
end
end
In your Company, News, Profile models call the concern...
class Company < ActiveReecord::Base
include ImageHandling
...
end
I have created a simple railtie, adding a bunch of stuff to ActiveRecord:
0 module Searchable
1 class Railtie < Rails::Railtie
2 initializer 'searchable.model_additions' do
3 ActiveSupport.on_load :active_record do
4 extend ModelAdditions
5 end
6 end
7 end
8 end
I require this file (in /lib) by adding the following line to config/environment.rb before the application is called:
require 'searchable'
This works great with my application and there are no major problems.
I have however encountered a problem with rake db:seed.
In my seeds.rb file, I read data in from a csv and populate the database. The problem I am having is that the additions I made to ActiveRecord don't get loaded, and seeds fails with a method_missing error. I am not calling these methods, but I assume that since seeds.rb loads the models, it tries to call some of the methods and that's why it fails.
Can anyone tell me a better place to put the require so that it will be included every time ActiveRecord is loaded (not just when the full application is loaded)? I would prefer to keep the code outside of my models, as it is code shared between most of my models and I want to keep them clean and DRY.
Putting the extend there just adds it to ActiveRecord::Base.
When a model class is referenced, via Rails 3.1 autoloading/constant lookup, it will load the class. At that point, it is pure Ruby (nothing magic) as to what happens, basically. So I think you have at least a few options. The "bad" option that kind of does what you want it to hook into dependency loading. Maybe something like:
module ActiveSupport
module Dependencies
alias_method(:load_missing_constant_renamed_my_app_name_here, :load_missing_constant)
undef_method(:load_missing_constant)
def load_missing_constant(from_mod, const_name)
# your include here if const_name = 'ModelName'
# perhaps you could list the app/models directory, put that in an Array, and do some_array.include?(const_name)
load_missing_constant_renamed_my_app_name_here(from_mod, const_name)
end
end
end
Another way to do it would be to use a Railtie like you were doing and add a class method to ActiveRecord::Base that then includes stuff, like:
module MyModule
class Railtie < Rails::Railtie
initializer "my_name.active_record" do
ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base gets new behavior
include ::MyModule::Something # where you add behavior. consider using an ActiveSupport::Concern
end
end
end
end
If using an ActiveSupport::Concern:
module MyModule
module Something
extend ActiveSupport::Concern
included do
# this area is basically for anything other than class and instance methods
# add class_attribute's, etc.
end
module ClassMethods
# class method definitions go here
def include_some_goodness_in_the_model
# include or extend a module
end
end
# instance method definitions go here
end
end
Then in each model:
class MyModel < ActiveRecord::Base
include_some_goodness_in_the_model
#...
end
However, that isn't much better than just doing an include in each model, which is what I'd recommend.