Why can't I use a private method from within my class? How do I fix my code to prevent the error?
module CarRegistration
class Basics < Base
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
private
car_structure = #array of hashes
def fields_of_model(model)
car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end
end
error
NoMethodError (undefined method `fields_of_model' for
CarRegistration::Basics:Class):
I think you have a number of problems going on here.
First, you've defined fields_of_model as an instance method, here:
def fields_of_model(model)
car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end
but you're trying to call it from the class, here:
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
So, you'll want to make fields_of_model a class method, and define it before you call it. Something like:
module CarRegistration
class Basics < Base
private
car_structure = #array of hashes
class << self
def fields_of_model(model)
car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end
end
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
end
You'll also have problems with that car_structure variable, I think, because it'll be out of scope for the class method. So, I think you need to make a class-level instance variable. So, give this a try:
module CarRegistration
class Basics < Base
#car_structure = #array of hashes
class << self
def fields_of_model(model)
#car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end
private :fields_of_model
end
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
end
Note that I made the class method, :fields_of_models private using private :fields_of_model.
To demonstrate the whole thing, I ginned up this RSpec test:
require 'rails_helper'
class Car
attr_accessor *%w(
color
make
year
).freeze
end
module CarRegistration
class Basic
#car_structure = [
{model: :car, name: :color},
{model: :car, name: :make},
{model: :car, name: :year}
]
class << self
def fields_of_model(model)
#car_structure.select {|record| record[:model] == model}.map{|record| record[:name]}
end
private :fields_of_model
end
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
def car
#car ||= Car.new
end
end
end
RSpec.describe CarRegistration::Basic do
it "has :fields_of_model as a private class method" do
expect(CarRegistration::Basic.public_methods).not_to include(:fields_of_model)
expect(CarRegistration::Basic.private_methods).to include(:fields_of_model)
end
it "responds to :color and :color=" do
expect(car_registration).to respond_to(:color)
expect(car_registration).to respond_to(:color=)
end
it "sets and gets attributes on car" do
expect(car_registration.color).to be_nil
expect(car_registration.car.color).to be_nil
car_registration.color = :red
expect(car_registration.car.color).to eq(:red)
expect(car_registration.color).to eq(:red)
expect(car_registration.instance_variable_get(:#color)).to be_nil
end
end
def car_registration
#car_registration ||= described_class.new
end
Which, when run, yields:
CarRegistration::Basic
has :fields_of_model as a private class method
responds to :color and :color=
sets and gets attributes on car
Finished in 0.733 seconds (files took 27.84 seconds to load)
3 examples, 0 failures
BTW, having this code in your class outside of a def-end is just fine and not the root of your problem. In fact, it's quite normal.
Also, I will note that Jörg W Mittag wishes to say:
I am one of those Ruby Purists who likes to point out that there is no such thing as a class method in Ruby. I am perfectly fine, though, with using the term class method colloquially, as long as it is fully understood by all parties that it is a colloquial usage. In other words, if you know that there is no such thing as a class method and that the term "class method" is just short for "instance method of the singleton class of an object that is an instance of Class", then there is no problem. But otherwise, I have only seen it obstruct understanding.
Let it be fully understood by all parties that the term class method is used above in its colloquial sense.
Because you wrote the method not in def-end clause; you should write it like
def my_method
fields_of_model(:car).each do |attr|
delegate attr.to_sym, "#{attr}=".to_sym, to: :car
end
end
That is why the error message says CarRegistration::Basics:Class as opposed to CarRegistration::Basics
Here is a sample code that works.
Usually there is no need to put a class inside Module, but if you must for some reason, this is a way.
module CarRegistration
class Basics < Object
def run(model)
fields_of_model(model)
end
private
def fields_of_model(model)
puts model
end
end
end
a = CarRegistration::Basics.new
a.run('xyz') # => 'xyz' is printed.
Related
How can I dynamically create methods like this using ruby metaprogramming ?
class CommentBridge < Bridge
def id(comment)
comment.id
end
def message(comment)
comment.message
end
def votes_count(comment)
comment.votes_count
end
end
I tried this but it is not working.
['id', 'message', 'votes_count'].each do |method|
define_method "#{method}" do |parameter|
method(parameter.method)
end
end
You should use public_send to call methods based on their name:
['id', 'message', 'votes_count'].each do |method|
define_method "#{method}" do |parameter|
parameter.public_send(method)
end
end
I do not think that you need different comment every time (probably you do). So I'd recommend to simply get rid of this comment argument.
There are the options.
Using RubyOnRails (I see you question is tagged so) you can use delegate (as #SimpleLime has already commented)
class CommentBridge < Bridge
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
delegate :id, :message, :votes_count, to: :comment
end
In case of pure Ruby 2 use Forwardable:
class CommentBridge
extend Forwardable
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
def_delegators :comment, :id, :message, :votes_count
end
If you want to provide additional methods on top of you comment object and forward all the rest methods use SimpleDelegator (assuming that this Brigde in namgin means that your class is just a wrapper):
class CommentDecorator < SimpleDelegator
def hello
'hello'
end
end
comment = Commend.find(params[:id])
decorated_comment = CommentDecorator.new(comment)
You can also define method missing:
class CommentBridge < Bridge
attr_reader :comment
def initialize(comment_)
#comment = comment_)
end
def method_missing(m, *args)
if [:id, :message, :comment].include?(m)
comment.public_send(method, *args)
else
super
end
end
end
Finally, you can create your own delegation-DSL on top of define_method, but I think this is the extra in that case.
I don't think that method_missing or define_method inside loop is neat although it works.
I have a non activerecord rails model:
class Document
attr_accessor :a, :b
include ActiveModel::Model
def find(id)
initialize_parameters(id)
end
def save
...
end
def update
...
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
end
In order to find the Document, I can use:
Document.new.find(3)
So, to get it directly I changed the find method to
def self.find(id)
initialize_parameters(id)
end
And I get the following error when I run
Document.find(3)
undefined method `initialize_parameters' for Document:Class
How can I make this work?
You can't access an instance method from a class method that way, to do it you should instantiate the class you're working in (self) and access that method, like:
def self.find(id)
self.new.initialize_parameters(id)
end
But as you're defining initialize_parameters as a private method, then the way to access to it is by using send, to reach that method and pass the id argument:
def self.find(id)
self.new.send(:initialize_parameters, id)
end
private
def initialize_parameters(id)
#a = 1
#b = 2
end
Or just by updating initialize_parameters as a class method, and removing the private keyword, that wouldn't be needed anymore.
This:
class Document
attr_accessor :a, :b
def self.find(id)
initialize_parameters(id)
end
end
Is not trying to "access class method from instance method" as your title states. It is trying to access a (non-existent) class method from a class method.
Everything Sebastian said is spot on.
However, I guess I would ask: 'What are you really trying to do?' Why do you have initialize_parameters when ruby already gives you initialize that you can override to your heart's content? IMO, it should look something more like:
class Document
attr_accessor :a, :b, :id
class << self
def find(id)
new(id).find
end
end
def initialize(id)
#a = 1
#b = 2
#id = id
end
def find
# if you want you can:
call_a_private_method
end
private
def call_a_private_method
puts id
end
end
I'm trying to access variables defined in class One, through inheritance, in class Two. I can't seem to find the right way of going about it - it seems to work for methods:
class One
class << self
def test
puts "I'm a method from class one"
end
end
end
end
And as a new object the variable is accessible:
class Two < One
test
end
#=> I'm a method from class one
class Test
attr_accessor :a
def initialize
#a = "hi"
end
end
Test.new.a
#=> "hi"
But I'm trying to do something like:
class One
class << self
a = "hi"
end
end
class Two < One
a
end
#=> NameError: undefined local variable or method `a' for Two:Class
For now I'm using class variables, but I'm sure there's a better way:
class One
##a = "hi"
end
class Two < One
##a
end
#=> "hi"
local and class instance variables wouldn't be accessible through inheritance in Ruby.
Limosine is an example of a class inheriting, a variable (brand) and a method, to_s
class Car
def initialize(brand)
#brand = brand
end
def to_s
"(##brand, ##model)"
end
end
class Limosine < Car
def initialize(brand, model)
super(brand)
#model = model
end
end
Use:
puts Merc.new("Mercedes", "Maybach")to_s
I'm creating a module that extends the functionality of an ActiveRecord model.
Here's my initial setup.
My class:
class MyClass < ActiveRecord::Base
is_my_modiable
end
And Module:
module MyMod
def self.is_my_modiable
class_eval do
def new_method
self.mod = true
self.save!
end
end
end
end
ActiveRecord::Base(extend,MyMod)
What I would like to do now is extend the functionality of the new_method by passing in a block. Something like this:
class MyClass < ActiveRecord::Base
is_my_modiable do
self.something_special
end
end
module MyMod
def self.is_my_modiable
class_eval do
def new_method
yield if block_given?
self.mod = true
self.save!
end
end
end
end
This doesn't work though, and it makes sense. In the class_eval, the new_method isn't being executed, just defined, and thus the yield statement wouldn't get executed until the method actually gets called.
I've tried to assign the block to a class variable within the class_eval, and then call that class variable within the method, but the block was being called on all is_my_modiable models, even if they didn't pass a block into the method.
I might just override the method to get the same effect, but I'm hoping there is a more elegant way.
If I understood you correctly, you can solve this by saving passed block to an instance variable on class object and then evaling that in instance methods.
bl.call won't do here, because it will execute in the original context (that of a class) and you need to execute it in scope of this current instance.
module MyMod
def is_my_modiable(&block)
class_eval do
#stored_block = block # back up block
def new_method
bl = self.class.instance_variable_get(:#stored_block) # get from class and execute
instance_eval(&bl) if bl
self.mod = true
self.save!
end
end
end
end
class MyClass
extend MyMod
is_my_modiable do
puts "in my modiable block"
self.something_special
end
def something_special
puts "in something special"
end
attr_accessor :mod
def save!; end
end
MyClass.new.new_method
# >> in my modiable block
# >> in something special
You can do this by assigning the block as a method parameter:
module MyMod
def self.is_my_modiable
class_eval do
def new_method(&block)
block.call if block
self.mod = true
self.save!
end
end
end
end
I have defined a module to extend ActiveRecord.
In my case I have to generate instance methods with the symbols given as arguments to the compound_datetime class method. It works when class_eval is called outside the each block but not inside it; in the latter case I get an undefined method error.
Does anyone know what I am doing wrong?
module DateTimeComposer
mattr_accessor :attrs
##attrs = []
module ActiveRecordExtensions
module ClassMethods
def compound_datetime(*attrs)
DateTimeComposer::attrs = attrs
include ActiveRecordExtensions::InstanceMethods
end
end
module InstanceMethods
def datetime_compounds
DateTimeComposer::attrs
end
def self.define_compounds(attrs)
attrs.each do |attr|
class_eval <<-METHODS
def #{attr.to_s}_to()
puts 'tes'
end
METHODS
end
end
define_compounds(DateTimeComposer::attrs)
end
end
end
class Account < ActiveRecord::Base
compound_datetime :sales_at, :published_at
end
When I try to access the method:
Account.new.sales_at_to
I get a MethodError: undefined method sales_at_to for #<Account:0x007fd7910235a8>.
You are calling define_compounds(DateTimeComposer::attrs) at the end of the InstanceMethods module definition. At that point in the code, attrs is still an empty array, and self is the InstanceMethods module.
This means no methods will be defined, and even if they were, they would be bound to InstanceMethods's metaclass, making them class methods of that module, not instance methods of your Account class.
This happens because method calls inside the InstanceMethods module definition are evaluated as they are seen by the ruby interpreter, not when you call include ActiveRecordExtensions::InstanceMethods. An implication of this is that it is possible to run arbitrary code in the most unusual of places, such as within a class definition.
To solve the problem, you could use the included callback provided by ruby, which is called whenever a module is included in another:
module InstanceMethods
# mod is the Class or Module that included this module.
def included(mod)
DateTimeComposer::attrs.each do |attr|
mod.instance_eval <<-METHODS
def #{attr.to_s}_to
puts 'tes'
end
METHODS
end
end
end
As an additional suggestion, you should be able to achieve the same result by simply defining the methods when compound_datetime is called, thus eliminating the dependence on the attrs global class variable.
However, if you must have access to the fields which were declared as compound datetime, you should use class instance variables, which are unique to each class and not shared on the hierarchy:
module ClassMethods
def compound_datetime(*attrs)
#datetime_compounds = attrs
attrs.each do |attr|
instance_eval <<-METHODS
def #{attr.to_s}_to
puts 'tes'
end
METHODS
end
end
def datetime_compounds; #datetime_compounds; end;
end
class Account < ActiveRecord::Base
compound_datetime :sales_at, :published_at
end
class AnotherModel < ActiveRecord::Base
compound_datetime :attr1, :attr2
end
Account.datetime_compounds
=> [ :sales_at, :published_at ]
AnotherModel.datetime_compounds
=> [ :attr1, :attr2 ]