Undefined method in ActiveSupport concern - ruby-on-rails

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

Related

Can't access to attr_accessor in Concern Ruby on Rails

I want to register the class method in concern and access to attr_accessor, but it doesn't work. This is my sample code. Please help me how can I do this. Thank you so much!
app/controllers/concerns/foobar_concern.rb
module FoobarConcern
extend ActiveSupport::Concern
included do
class << self
attr_accessor :foo
end
end
class_methods do
def test_method(bar)
self.foo = bar
end
end
end
app/controllers/foobar_controller.rb
class FoobarController < ApplicationController
include FoobarConcern
test_method 'Just test'
def index
self.foo => NoMethodError: undefined method "foo"
foo => NameError: undefined local variable or method "foo"
end
end
Just delegate required methods to the class like this
module FoobarConcern
extend ActiveSupport::Concern
included do
delegate :foo, :foo=, to: :class
class << self
attr_accessor :foo
end
end
end
The issue is that you're defining a method at the class level (FoobarController.foo) but calling it on an instance of the class (FoobarController.new.foo).
One option is to call the foo method on the class instead:
def index
self.class.foo
end
You can also define an accessor method for instances of the class like:
module FoobarConcern
extend ActiveSupport::Concern
included do
class << self
attr_accessor :foo
end
end
class_methods do
def test_method(bar)
self.foo = bar
end
end
# -- NEW ---
# This `foo` method is defined for instances of the class and calls the class method.
def foo
self.class.foo
end
end

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.

How to access a class method (or variable) in an instance method using concern?

I need to access a class method (defined in ClassMethods) in an instance method inside a concern.
My brain is melted and I'm sure that is a simple thing that I'm doing wrong.
I need to access comparable_opts inside comparison. How can I do it?
Follow snippets below:
Concern
# app/models/concerns/compare.rb
module Compare
extend ActiveSupport::Concern
attr_accessor :comparable_opts
module ClassMethods
attr_reader :arguable_opts
def comparable_opts
##comparable_opts
end
private
def default_opts
#default_opts ||= {fields: [:answers_count,
:answers_correct_count,
:answers_correct_rate,
:users_count]}
end
def compare(opts={})
#comparable_opts = default_opts.merge(opts)
end
end
def comparison
end
end
Model
# app/models/mock_alternative.rb
class MockAlternative < ActiveRecord::Base
include Compare
belongs_to :mock, primary_key: :mock_id, foreign_key: :mock_id
compare fields: [:answers_count, :question_answers_count, :question_answers_rate],
with: :mock_aternative_school
def question_answers_rate
self[:answers_count].to_f/self[:question_answers_count].to_f
end
end
Solution:
I've just used cattr_accessor in my method compare. Thank everyone.
module Compare
extend ActiveSupport::Concern
module ClassMethods
attr_reader :arguable_opts
def comparison_klass
"ActiveRecord::#{comparable_opts[:with].to_s.classify}".constantize
end
private
def default_opts
#default_opts ||= {fields: [:answers_count,
:answers_correct_count,
:answers_correct_rate,
:users_count]}
end
def compare(opts={})
cattr_accessor :comparable_opts
self.comparable_opts = default_opts.merge(opts)
end
end
def comparison
comparable_opts
end
end

how to extend belong_to functionality in rails

I'm building a gem and I want part of its functionality to extend ActiveRecord::Associations::Builder::BelongsTo but I cannot figure out how to do it
so basically user should be able to specify:
class Event < ActiveRecord::Base
belongs_to :users, foo: true
end
anyone know how to do it ??
This wont work:
module Mygem
module BelongsToFoo
def valid_options
super + [:foo]
end
#... other functionality
end
end
class ActiveRecord::Associations::Builder::BelongsTo
extend MyGem::BelongsToFoo
end
console
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
#=> false ... :(
Event
ArgumentError: Unknown key: foo
belongs_to source code
=============================================================================
Update
flowing delwyns answer I tried to have a another look on my code and he is right it should be included however ActiveRecord::Associations::Builder::BelongsTo has a variable valid_options as well.
so I can do
ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c).valid_options.include? :foo
# => true
but also
ActiveRecord::Associations::Builder::BelongsTo.valid_options.include? :foo
# => true
so it should really look like this
module MyGem
module BelongsToFoo
extend ActiveSupport::Concern
included do
self.valid_options += [:foo]
end
def valid_options
super + [:foo]
end
def define_callbacks(model, reflection)
# this wont get executed
add_foo_callbacks(model, reflection)# if options[:foo]
super
end
def add_foo_callbacks(model, reflection)
# therefore this wont either
end
end
end
Even if I try this
module MyGem
module BelongsToFoo
def define_callbacks(model, reflection)
raise "dobugging"
end
end
end
nothing will happen, Rails completely ignore my method override
So yes I can define my own option, however they do nothing :( any suggestions ?
There is a built in approach for extending association proxies, see http://guides.rubyonrails.org/association_basics.html#association-extensions
class Event < ActiveRecord::Base
belongs_to :users, :extend => MyGem::SpecialTouch
end
module MyGem
module SpecialTouch
def touch
# do the magic
end
end
end
Then you could of course override or alias chain belongs_to so that it pops your :foo option from options hash, converts it to proper :extend => ... and (really or effectively) calls belongs_to.
valid_options is an instance method so you need to use include instead of extend.
module MyGem
def valid_options
super + [:foo]
end
end
class ActiveRecord::Associations::Builder::BelongsTo
include ::MyGem
end
relation = ActiveRecord::Associations::Builder::BelongsTo.new(:a, :b, :c)
relation.valid_options.include? :foo
#=> true
Hope that helps.

Initialize a Ruby class depending on what modules are included

I'm wondering what is the best way to initialize a class in ruby depending on modules included. Let me give you an example:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
class GroupSearch < BaseSearch
include Search::Geolocalisable
def initialize query, options
end
end
module Search::Geolocalisable
extend ActiveSupport::Concern
included do
attr_accessor :where, :user_location #...
end
end
What I don't want, is having to initialize the :where and :user_location variables on each class that include the geolocalisable module.
Currently, I just define methods like def geolocalisable?; true; end in my modules, and then, I initialize these attributes (added by the module) in the base class:
class BaseSearch
def initialize query, options
#page = options[:page]
#...
if geolocalisable?
#where = query[:where]
end
end
end
class EventSearch < BaseSearch
#...
def initialize query, options
#...
super query, options
end
end
Is there better solutions? I hope so!
Why not override initialize in the module? You could do
class BaseSearch
def initialize query
puts "base initialize"
end
end
module Geo
def initialize query
super
puts "module initialize"
end
end
class Subclass < BaseSearch
include Geo
def initialize query
super
puts "subclass initialize"
end
end
Subclass.new('foo') #=>
base initialize
module initialize
subclass initialize
Obviously this does require everything that includes your modules to have an initialize with a similar signature or weird stuff might happen
See this code :
module Search::Geolocalisable
def self.included(base)
base.class_eval do
attr_accessor :where, :user_location #...
end
end
end
class EventSearch < BaseSearch
include Search::Geolocalisable
end

Resources