Rails, undefined method in Class but method is present - ruby-on-rails

I'm trying to solve a strange issue.
I'm extending ActiveRecord using a module.
module StringyAssociationIds
def stringy_ids(association)
define_method("stringy_#{association}_ids=") do |comma_seperated_ids|
self.send("#{association}_ids=", comma_seperated_ids.to_s.split(","))
end
define_method("stringy_#{association}_ids") do
send("#{association}_ids").join(",")
end
end
end
ActiveRecord::Base.extend(StringyAssociationIds)
I have a class "Gate" where I have an association.
class Gate < ActiveRecord::Base
include Productable
stringy_ids :product
end
The association is defined with a join table:
module Productable
extend ActiveSupport::Concern
included do
has_many :productable_products, as: :productable
has_many :products, through: :productable_products
end
end
When I try to create a new Gate I have an error:
undefined method `stringy_ids' for #<Class:0x007f91e12bb7e8>
Where is my fault?
Edit: I try also to add an extension inside the lib directory (autoloaded by application.rb)
module ActiveRecordExtension
extend ActiveSupport::Concern
def stringy_ids(association)
define_method("stringy_#{association}_ids=") do |comma_seperated_ids|
self.send("#{association}_ids=", comma_seperated_ids.to_s.split(","))
end
define_method("stringy_#{association}_ids") do
send("#{association}_ids").join(",")
end
end
end
# include the extension
ActiveRecord::Base.send(:include, ActiveRecordExtension)
I try also in console:
ActiveRecordExtension.instance_methods
=> [:stringy_ids]
So my extension is loaded...

Your class method stringy_ids is defined on ActiveRecord::Base, not Gate. Unlike instance methods, class methods are not inherited because the singleton class of Gate is not a subclass of the singleton class of ActiveRecord::Base.

StringyAssociationIds is not extended.
Actually, ActiveRecord::Base.extend(StringyAssociationIds) does not run. Move this code in config/initializer

Related

Undefined module method in model

lib/modules/file_type.rb
module Modules
module Type
def friend_name(type:)
...
end
end
end
app/models/car.rb
class Car < ApplicationRecord
include Modules::Type
def self.to_array
...
name = friend_name(type: 'test')
...
end
end
But I am getting this error:
undefined method `friend_name'
I am not sure why I am getting this error.
Anyone can help me?
If friend_name is a class method then instead of include use extend in Car model
extend Modules::Type
More info about difference between include and extend could be found here -
What is the difference between include and extend in Ruby?
Hope that helps!

How to meta program methods from a Class Constant array which is being referenced in a module?

I wrote a basic Eventable module that allows other Classes to create events for itself. All the Models have their own set of events such as a Message being sent or a User having a sign_in event. I would like to create an array of events as a constant at the class level and access that array inside the module to build helper methods for creating events like so:
class User
EVENT_TYPES = ['sign_in', 'sign_out']
include Eventable
end
class Message
EVENT_TYPES = ['sent', 'opened']
include Eventable
end
module Eventable
extend ActiveSupport::Concern
included do
has_many :events, as: :eventable
attr_accessor :metadata
self::EVENT_TYPES.each do |event_type|
define_method "create_#{event_type}_event" do |args={}|
Event.create(eventable: self, event_type: event_type)
end
end
end
end
The issue with this is: the methods are not being defined properly. It cannot reference the respective Model's constant EVENT_TYPES properly.
How can I access a class constant from insude a module?
Using constant like that is not very rubyesque, I'd say. This is much more idiomatic (in rails, at least).
class Message
include Eventable
event_types :sent, :opened
end
And here's the implementation
module Eventable
extend ActiveSupport::Concern
module ClassMethods
def event_types(*names)
names.each do |event_type|
define_method "create_#{event_type}_event" do |args={}|
Event.create(eventable: self, event_type: event_type)
end
end
end
end
end
Constants can be obtained by Module#const_get method. But I would not present the event types for individual classes as constants, but as either class methods or instance variables belonging to the class:
class User
def self.event_types
[:sign_in, :sign_out]
end
end
# or
class User
#event_types = [:sign_in, :sign_out]
class << self
attr_reader :event_types
end
end
It seems that another answer expressed most of the remaining things I wanted to say. But one more thing: I would not name the module Eventable, but eg. HavingEvents or simply Events.

Module classes in lib folder

I have a lib file lister_extension.rb
module ListerExtension
def lister
puts "#{self.class}"
end
end
And Post model
class Post < ActiveRecord::Base
has_many :reviews
extend ListerExtension
def self.puts_hello
puts "hello123123"
end
end
All is good when I call this in rails c:
2.1.1 :003 > Post.lister
Class
=> nil
But what happens when I want to add a class to my module?
For example:
module ListerExtension
class ready
def lister
puts "#{self.class}"
end
end
end
I get this error
TypeError: wrong argument type Class (expected Module)
When I call Post.first in rails c
From the doc for extend:
Adds to obj the instance methods from each module given as a
parameter.
Hence, you can't access this class through extended class. Have a look into including modules instead of extending them (read about ActionSupport::Concern module as well) or have a go with self.extended method (ref here)
TL;DR , in ruby you can´t extend with classes, you extend/include with modules
regards
updated: example for concern
include / extend with activesupport concern
module Ready
extend ActiveSupport::Concern
# this is an instance method
def lister
....
end
#this are class methods
module ClassMethods
def method_one(params)
....
end
def method_two
....
end
end
end
then in a ActiveRecord Model , like Post
class Post < AR
include Ready
end
with this procedure you will get the instance methods and class methods for free, also you can set some macros like when use a included block,
module Ready
extend ActiveSupport::Concern
included do
has_many :likes
end
end
hope that helps,
regards

ruby include vs extend

I am trying to abstract some of the logic required for cropping images into a module so that its not messing up my models. The code is based on http://railscasts.com/episodes/182-cropping-images
module CroppableImage
def croppable_image(*image_names)
image_names.each do |image_name|
define_method "#{image_name}_sizes" do
{ :cropped => read_attribute("#{image_name}_size").to_s, :large => "800x800>" }
end
define_method "#{image_name}_geometry" do |style|
style ||= :original
#geometry ||= {}
#geometry[style] ||= Paperclip::Geometry.from_file(eval "#{image_name}.path(style)")
end
define_method "#{image_name}_aspect_ratio" do
width, height = read_attribute("#{image_name}_size").split("x")
width.to_f / height.to_f
end
private
define_method "reprocess_#{image_name}" do
eval "#{image_name}.reprocess!"
end
end
end
end
To include this into my model it seems that I have to use extend. I thought extend was for including class methods. I am from a java background - I thought using extend basically created static method on the class.
class Website < ActiveRecord::Base
extend CroppableImage
croppable_image :logo, :footer_image
-- this works
It seems then that what I am really wanting is to create instance methods.
class Website < ActiveRecord::Base
include CroppableImage
croppable_image :logo, :footer_image
-- This throws the error "undefined method `croppable_image' for #"
Can anyone please explain whats going on and if I should be using include or extend in this case. Thanks guys
extend M internally is similar to class << self; include M; end - extend includes module into singleton class of an object (and makes instance methods of the module to be a singleton methods of a class you extend).
In your case you call croppable_image in the context of a class definition and thus croppable_image should be an instance method of a Class class or a singleton method of Website class.
This is why you should extend Website class with a module CroppableImage by using extend CroppableImage - it adds instance method croppable_image as a singleton method of Website class.
you can use both logic together.
Ruby has callbacks for extend and include
Example of using included callback
module CroppableImage
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def bar
puts 'class method'
end
end
def foo
puts 'instance method'
end
end

Ruby on Rails: shared method between models

If a few of my models have a privacy column, is there a way I can write one method shared by all the models, lets call it is_public?
so, I'd like to be able to do object_var.is_public?
One possible way is to put shared methods in a module like this (RAILS_ROOT/lib/shared_methods.rb)
module SharedMethods
def is_public?
# your code
end
end
Then you need to include this module in every model that should have these methods (i.e. app/models/your_model.rb)
class YourModel < ActiveRecord::Base
include SharedMethods
end
UPDATE:
In Rails 4 there is a new way to do this. You should place shared Code like this in app/models/concerns instead of lib
Also you can add class methods and execute code on inclusion like this
module SharedMethods
extend ActiveSupport::Concern
included do
scope :public, -> { where(…) }
end
def is_public?
# your code
end
module ClassMethods
def find_all_public
where #some condition
end
end
end
You can also do this by inheriting the models from a common ancestor which includes the shared methods.
class BaseModel < ActiveRecord::Base
def is_public?
# blah blah
end
end
class ChildModel < BaseModel
end
In practice, jigfox's approach often works out better, so don't feel obligated to use inheritance merely out of love for OOP theory :)

Resources