Uninitialized constant error when include a module - ruby-on-rails

Happened on ruby 3.1.2 & rails 7.0.4
module:
app/models/concerns/password_regeneratable.rb
module PasswordRegeneratable
extend ActiveSupport::Concern
module ClassMethods
def generate_password
SecureRandom.urlsafe_base64(self.class::DEFAULT_PASSWORD_LENGTH)
end
end
end
model:
app/models/user.rb
class User < ApplicationRecord
include PasswordRegeneratable
DEFAULT_PASSWORD_LENGTH = 30
end
When I try to call User.generate_password it gives me this error:
/app/app/models/concerns/password_regeneratable.rb:6:in `generate_password': uninitialized constant Class::DEFAULT_PASSWORD_LENGTH (NameError)
Did you mean? Class::DEFAULT_SETTINGS
I want to use module so I can reuse this generate_password method and possible some other methods for other model classes in the future. Is this kind of implementation correct?

module PasswordRegeneratable
extend ActiveSupport::Concern
module ClassMethods
def generate_password
SecureRandom.urlsafe_base64(self::DEFAULT_PASSWORD_LENGTH)
end
end
end
In a class method self is the class. Thus self.class gives you the confusingly named Class class since in Ruby classes are instances of Class.

Related

Ruby On Rails : Calling class method from concern

In Rails, is it possible to call methods from the class that included the concern, in the concern itself ? ie:
class Foo < ApplicationRecord
include Encryptable
def self.encrypted_attributes
%i[attr_1 attr_2]
end
end
module Encryptable
extend ActiveSupport::Concern
included do
self.encrypted_attributes do |attr|
define_method("#{attr}=") do |arg|
# do some stuff
end
define_method("#{attr}") do
# do some stuff
end
end
end
end
The issue is, when I try to do that, I get an error like :
*** NoMethodError Exception: undefined method 'encrypted_attributes' for #<Class:0x00005648d71c2430>
And, when debugging inside the concern, I get this something like this :
(byebug) self
Foo (call 'Foo' to establish a connection)
(byebug) self.class
Class
Ruby is a scripting language and the order matters. The following would do:
class Foo < ApplicationRecord
def self.encrypted_attributes
%i[attr_1 attr_2]
end
# OK, now we have self.encrypted_attributes defined
include Encryptable
end
More info: ActiveSupport::Concern#included.

Undefined method in ActiveSupport concern

I have a model that extends ActiveRecord::Base and includes a concern:
class User < ActiveRecord::Base
include UserConcern
def self.create_user()
...
results = some_method()
end
end
UserConcern is stored in the concerns directory:
module UserConcern
extend ActiveSupport::Concern
def some_method()
...
end
end
I am getting a run-time error when I try to create a new user by calling the create_user method that looks like this:
undefined method 'some_method' for #<Class:0x000000...>
I have two questions about this:
Why is the some_method undefined? It seems to me that I am properly including it with the statement include UserConcern. Does it have something to do with my User class extending ActiveRecord::Base? Or maybe something to do with the fact that I am calling some_methods() from a class method (i.e. self.create_user())?
Why does the run-time error refer to #<Class:0x000000...> instead of to #<User:0x000000...>?
try it
models/concerns/user_concern.rb:
module UserConcern
extend ActiveSupport::Concern
def some_instance_method
'some_instance_method'
end
included do
def self.some_class_method
'some_class_method'
end
end
end
models/user.rb:
class User < ActiveRecord::Base
include UserConcern
def self.create_user
self.some_class_method
end
end
rails console:
user = User.new
user.some_instance_method
# => "some_instance_method"
User.some_class_method
# => "some_class_method"
User.create_user
# => "some_class_method"
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html

alias_method_chain in module

I want a module to alias_method_chain a method from the class it is included into. Here is how I wrote it:
module MyModule
self.included(base)
base.class_eval do
alias_method_chain :perform, :chain
end
end
def perform_with_chain(opts)
#Do some stuffs
perform_without_chain(opts)
#Do some other stuffs
end
end
class SomeClass
include MyModule
def perform(opts)
end
end
but this throws an error since, when the module is included, the perform method is not yet defined in SomeClass:
in `alias_method': undefined method `perform' for class `SomeClass' (NameError)
How should one write this pattern so the alias chain fully works?
Include after perform is defined.
class SomeClass
def perform(opts)
end
include MyModule
end

Rails mixins using ActiveSupport::Concern does not work

I have the following module/class defined in my lib folder
module Service::Log
extend ActiveSupport::Concern
module ClassMethods
def logger
Rails.logger
end
end
end
class Service::MyService
include Service::Log
end
When I try to invoke logger method via an object instance I get an error message - NoMethodError: undefined method `logger' for - Service::MyService:0x007fdffa0f23a0
Service::MyService.new.logger
What am I doing wrong? I am using Rails 4.0.2.
You are defining the logger method as a class method, instead of a normal method. This should work:
module Service::Log
extend ActiveSupport::Concern
def logger
Rails.logger
end
end
class Service::MyService
include Service::Log
end
Service::MyService.new.logger
The way you were defining the method before allowed you to use the logger method on the class directly, such as:
Service::MyService.logger

Rails: callbacks from module

I try to do this:
app/models/my_model.rb:
class MyModel < ActiveRecord::Base
include MyModule
...
end
lib/my_module.rb:
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
but I get an error:
undefined method `before_destroy' for MyModule:Module
How can I correct it.
Also I'm new to ruby. What type has these "attributes": before_destroy, validates, has_many?
Are they variables or methods or what?
Thanks
before_destroy, validates, etc. are not attributes or anything like that. These are method calls.
In ruby, the body of a class is all executable code, meaning that each line of the class body is executed by the interpeter just like a method body would.
before_destroy :my_func is a usual ruby method call. The method that gets called is before_destroy, and it receives a symbol :my_func as an argument. This method is looked up in the class (or module) in the scope of which it is called.
So moving on to your question, I think now you should understand that when the interpreter loads your module
module MyModule
before_destroy :my_func #!
def my_func
...
end
end
it starts executing its body and searches for the method before_destroy in this module and cannot find one. What you want to do is call this method not on the module, but rather on the class where the module is included. For that we have a common idiom using the Module#included method:
module MyModule
module InstanceMethods
def my_func
...
end
end
def self.included(base)
base.send :include, InstanceMethods
base.before_destroy :my_func
end
end
In lib/my_module.rb, do this:
class MyInheritedClass < ActiveRecord::Base
before_destroy :my_func
def my_func
...
end
end
In app/models/my_model.rb, do this:
class MyModel < MyInheritedClass
...
end
There is no before_destroy filter in the module you are trying to create above. What my code does is creating a class that will inherit from ActiveRecord::Base and that will be your template class which all your other classes can inherit from. The template class also contains all properties of ActiveRecord::Base.
u can correct this by removing before_destroy from MyModule and place it in MyModel instead
before_destroy and other callbacks are only available to classes which extends ActiveRecord::Base, more info here
hope this helps =)

Resources