How define_method uses variables initialised outside of it - ruby-on-rails

class Temp1
def add(s)
match = 'test'
self.class.class_eval do
define_method(s) do
puts match
end
end
#match ='haha'
end
end
As i thinks of it, 'match' is a local variable, so i don't understand how it can see it from another method, plus if uncomment #match ='haha', method will print 'haha' somehow. Can somebody explain it?
Also, i don't see difference here between using class_eval or instance_eval, seems like it do the same thing.
And, at last but not least, can I create class method here using define_method? So I can call it like Temp1.something instead of Temp1.new.something?

Because blocks (do...end) are closures and have access to their
surrounding scope.
You used block with class_eval,so it has the access to its surroundings,which is the scope of the method add. Now you use another block with define_method,which as also has the access to the scope of the method add,via the block with the class_eval.match local variable has been created inside the scope of the method add. So the blocks has the access to the variable.
And, at last but not least, can I create class method here using define_method?
No you can't.define_method Defines an instance method in the receiver.self.class is Temp1. Now under Temp1.class_eval do..end,you are defining instance methods of the class Temp1,with the method define_method.define_method is a private class method of all the classes,in which ancestor chain Object class present.
class C;end
C.private_methods.grep(/define_/)
# => [:define_method]
Also, i don't see difference here between using class_eval or instance_eval, seems like it do the same thing.
Okay! Let me explain for you. You can't see the difference here,as Teamp1 is a Class and also an instance of a Class. In both call class_eval and instance_eval,self is being set to Teamp1,by their respective definition as documented.
class C
def self.bar;11;end
def baz;12;end
end
C.is_a? Class # => true
C.instance_of? Class # => true
C.class_eval{ bar } # => 11
C.instance_eval{ bar } # => 11
Hope this helps!

Related

Is it possible to call a previously defined method from within a method of the same name?

Is it possible to overwrite a method and still fallback to the original method (given no superclass is involved)?
def User
def method
# do some original stuff
end
def method
# do some new stuff
call_the_original :method
end
end
Hopefully, my specific example will make my meaning more clear.
Using activestorage has_one_attached :avatar in a User model adds a setter method. I want to do some stuff when this setter is called, but I still want the original method to run.
class User
has_one_attached :avatar
# According to the source (see references) this mixes in the following setter method
def avatar=(attachable)
# do activestorage stuff
end
# I want to add some custom functions to this, before still running "do activestorage
# stuff". I could copy, paste and edit the entire function. But out of interest,
# I wondered if a more elegant solution exists.
def avatar=(attachable)
# do my stuff
super(attachable)
end
end
super obviously does not work because User is not inheriting from anything in which avatar=() is defined.
I could create e.g. MasterUser class containing has_one_attached and from which User inherits, but this seems overkill for just this particular case.
I could submit to a custom_avatar_method=(attachable) which calls avatar=(attachable).
But with this question what I'm really interested in is whether there a way to call a previously defined method from a method of the same name?
References:
#has_one_attached source
You can make use of alias_method to access the previous definition here:
class User
def avatar=(attachable)
# do activestorage stuff
end
alias_method :original_avatar=, :avatar=
def avatar=(attachable)
# do my stuff
self.original_avatar=(attachable)
end
end
Another option is saving the old method inside a variable before defining the new method with the same name. Then call the variable from inside the newly defined method:
class User
def avatar=(attachable)
# do activestorage stuff
end
instance_method(:avatar=).tap do |avatar_eq|
define_method(:avatar=) do |attachable|
# do my stuff
avatar_eq.bind(self).call(attachable)
end
end
end
In the above example define_method(:avatar=) has to be used, since a regular def avatar= wont let you access the avatar_eq variable.
The code is somewhat more complicated than JagdeepSinghs answer, but leaves the class less cluttered with methods. The old method is no longer defined and thus can no longer be called by itself.
References:
Module#instance_method to get the previously defined method
Object#tap to namespace a variable to a small portion of the class definition
Module#define_method to define the new method with the same name
UnboundMethod#bind to bind the unbound method to the current User instance
Method#call to call the bound previously defined method

Ruby variables visibility inside modules

TL;DR;
I need to make some ##vars of a static method (extends) in one module visible to a instance method in another module(includes).
How to accomplish that once only setting ##var=value was not enough to make it visible?
Maybe you can just read my capitalized comment bellow and jump to question 4.
Hi, I would like to add an method to my models to index some data in a mysql table with some full text search fields.
In order to accomplish that, I created the following module:
module ElasticFakeIndexing
module IndexingTarget
#instance method to be called on model to get data to save
def build_index_data
{
entity_id: self.id,
entity_type: self.class.name,
#UNABLE TO ACCESS IF SET ONLY WITH ##var=value. Why?
#AND ALMOST SURE THAT USING class_variable_set IS THE CAUSE OF CONFIGURATION OF ONE MODULE MESSING UP WITH ANOTHER'S
title: ##title_fields.collect{|prop| self.send(prop.to_sym)}.join(" || "),
description: ##description_fields.collect{|prop| self.send(prop.to_sym)}.join(" || "),
}
end
def self.included(base)
base.extend ClassMethods
end
module ClassMethods
#class method to declare/call at a given model
def elastic_fake(options = {})
#Make sure we always get an array so we can use 'join'
title_arg = Array(options[:title])
ElasticFakeIndexing::IndexingTarget.class_variable_set(:##title_fields, title_arg)
description_arg = Array(options[:description])
ElasticFakeIndexing::IndexingTarget.class_variable_set(:##description_fields, description_arg)
extra_arg = Array(options[:extra])
ElasticFakeIndexing::IndexingTarget.class_variable_set(:##extra_args, extra_arg)
end
end
end
end
And I use it this way at my models:
class SomeModel < ApplicationRecord
#includes the module
include ElasticFakeIndexing::IndexingTarget
...
# 'static' method call to configure to all classes of this model
elastic_fake(title: "prop_a", description: ["prop_b", "prop_c", "prop_d"])
end
And at some point of my code something like this will be called:
index_data = some_model_instance.build_index_data
save_on_mysql_text_search_fields(index_data)
But I got some problems. And have some questions:
when I use/include my module in a second model, looks like the configuration of one model is being visible to the other. And I got 'invalid fields' like errors. I guess it happens because of this, for example:
ElasticFakeIndexing::IndexingTarget.class_variable_set(:##title_fields, title_arg)
But I got to this this because only set ##title_fields wasn't enough to make title_fields visible at build_index_data instance method. Why?
Why using only #title_fields isn't enough too to make it visible at build_index_data?
How to design it in a way that the set of fields are set in a 'static' variable for each model, and visible inside the instance method build_index_data? Or as a possible solution, the fields could live in a instance variable and be visible. But I think it should live in a 'static' variable because the fields will not change from one instance of the model to another...
Any thoughts? What am I missing about the variables scopes/visibility?
Thank you
Read the following articles on Ruby variables:
Ruby Variable Scope
Understanding Scope in Ruby
quick reminder: ##title_fields, class variable, must be initialized at creation time, while #title_fields, instance variable, hasn't such requirement.
Instead of relying on class variables I recommend using class side instance variables. Class variables will easily be overwritten between individual models including the module. Class side instance variables however are save.
Using some of the syntactic sugar (namely concern and class_attribute) rails offers you could write something like
module ElasticFakeIndexing
extend ActiveSupport::Concern
included do
class_attribute :title_fields,
:description_fields,
:extra_args
end
class_methods do
def elastic_fake(options = {})
...
self.title_fields = Array(options[:title])
...
end
end
def build_index_data
...
title: self.class.title_fields ...
...
end
end

How can I call methods within controller (RoR)?

I am very new to RoR and I have played around the source code. But I have a problem that I already built a 'def A' for creating first CSV file, and 'def B' for creating second CSV file. Each 'def' has its own button, but I have the third button to create all CSVs (to produce output from first and second CSV files.)
What is the possible way to do it?
def first_csv
...
end
def second_csv
..
end
def all_csv
<< how to call get first and second csv >>
end
Thanks in advance,
It should be as simple as you imagine:
def all_csv
first_csv
second_csv
end
Muntasim's answer is correct, but I have to add some additional information.
Ruby provides two types of methods..class methods and instance methods.
class MyClass < AvtiveRecord::Base
# class method
def self.foo
# do something
# within this scope the keyword "self" belongs to the class
end
# another class method which calls internally the first one
def self.bar
something = foo # self.foo could also be written
# do something with something
# within this scope the keyword "self" belongs to the class
end
# instance method
def foo
# do something
# if you use the keyword "self" within an instance method, it belongs to the instance
end
# another instance method which calls class and the first instance method
def bar
mystuff = Myclass.bar # if you want to call the class method you cannot use "self", you must directly call the class
mystuff2 = foo # self.foo is the same, because of the instance scope
return [mystuff, mystuff2]
end
end
You can call the last instance method like following
instance = MyClass.first
instance.bar

Wrapping an object with methods from another class

Let's say I have a model called Article:
class Article < ActiveRecord::Base
end
And then I have a class that is intended to add behavior to an article object (a decorator):
class ArticleDecorator
def format_title
end
end
If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:
article = ArticleDecorator.decorate(Article.top_articles.first) # for single object
or
articles = ArticleDecorator.decorate(Article.all) # for collection of objects
How would I go about implementing this decorate method?
What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?
Updated:
Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:
module ArticleDecorator
def format_title
"#{title} [decorated]"
end
def self.decorate(object_or_objects_to_decorate)
object_or_objects_to_decorate.tap do |objects|
Array(objects).each { |obj| obj.extend ArticleDecorator }
end
end
end
It does the same thing, but:
Avoids checking type of the arguments relying on Kernel#Array method.
Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.
Here is my code:
class ArticleDecorator < BasicObject
def self.[](instance_or_array)
if instance_or_array.respond_to?(:to_a)
instance_or_array.map {|instance| new(instance) }
else
new(instance_or_array)
end
end
attr_accessor :wrapped_article
def initialize(wrapped_article)
#wrapped_article = wrapped_article
end
def format_title
#wrapped_article.title.upcase
end
protected
def method_missing(method, *arguments)
#wrapped_article.method(method).call(*arguments)
end
end
You can now extend a single Article by calling
extended_article = ArticleDecorator[article]
or multiple articles by calling
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]
You can regain the original Article by calling
extended_article.wrapped_article
Or you can exchange the wrapped Article inside like this
extended_article = ArticleDecorator[article_a]
extended_article.format_title
# => "FIRST"
extended_article.wrapped_article = article_b
extended_article.format_title
# => "SECOND"
Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:
article.object_id
# => 123
extended_article = ArticleDecorator[article]
extended_article.object_id
# => 123
Notice though that BasicObject exists only in Ruby 1.9 and above.
You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.
In your case, sounds like you want to match up things like "format_.*" to their respective property getters.
Which part is tripping you up?
module ArticleDecorator
def format_title
"Title: #{title}"
end
end
article = Article.top_articles.first.extend(ArticleDecorator) # for single object
Should work fine.
articles = Article.all.extend(ArticleDecorator)
May also work depending on ActiveRecord support for extending a set of objects.
You may also consider using ActiveSupport::Concern.

why use include module when class_eval by itself would suffice

In the following code include module is used. The way I see it if include module is removed then also an instance method would be created. Then why user include module ?
http://github.com/rails/rails/blob/master/activerecord/lib/active_record/associations.rb#L1416
include Module.new {
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def destroy # def destroy
super # super
#{reflection.name}.clear # posts.clear
end # end
RUBY
}
First of all let's make one thing clear. When they call super inside the class_eval — it has absolutely nothing to do with why they used include Module.new {} thing. In fact the super which was called inside the destroy method is completely irrelevant to answering your question. There could be any arbitrary code inside that destroy method.
Now that we got it out of the way, here's what's going on.
In ruby, if you simply define a class method, and then define it again in the same class, you will not be able to call super to access the previous method.
For example:
class Foo
def foo
'foo'
end
def foo
super + 'bar'
end
end
Foo.new.foo # => NoMethodError: super: no superclass method `foo' for #<Foo:0x101358098>
This makes sense, because the first foo was not defined in some superclass, or anywhere up the lookup chain (which is where super points). However, you can define the first foo in such a way that when you later overwrite it — it will be available by calling super. This is exactly what they wanted to achieve with doing module include.
class Foo
include Module.new { class_eval "def foo; 'foo' end" }
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
This works, because when you include a module, ruby inserts it into the lookup chain. This way you can subsequently call super in the second method, and expect the included method to be called. Great.
However, you may wonder, why not simply include a module without all the tricks? Why are they using block syntax? We know that my above example is exactly equivalent to the following:
module A
def foo
'foo'
end
end
class Foo
include A
def foo
super + 'bar'
end
end
Foo.new.foo # => "foobar"
So why didn't they do that? The answer is — the call to reflection. They needed to capture the variable (or method) which was available in the current context, which is reflection.
Since they are defining the new module using block syntax, all the variables outside of the block are available for usage inside the block. Convenient.
Just to illustrate.
class Foo
def self.add_foo_to_lookup_chain_which_returns(something)
# notice how I can use variable something in the class_eval string
include Module.new { class_eval "def foo; '#{something}' end" }
end
end
# so somewhere else I can do
class Foo
add_foo_to_lookup_chain_which_returns("hello")
def foo
super + " world"
end
end
Foo.new.foo # => "hello world"
Neat, huh?
Now let me stress it again. The call to super inside of the destroy method in your example has nothing to do with any of the above. They called it for their own reasons, because maybe the class where this is happening is subclassing another class which already defined destroy.
I hope this made it clear.
I'm guessing but... they don't want to overwrite the "destroy" method, and want to leave it available to be overloaded by some end-user (you or me), without it removing this "reflection.clear" functionality.
So - by including it as a module, they can call "super" which will call the original destroy or the overloaded version (written by the end-user).
Thanks to include, destroy method is not overwritten. It lands in ghost class that actual class derives from. That way, when one will call destroy on AR object, original one will be called, and super will call one from anonymous module (which will later call original destroy from class that it derived from).
A bit tricky, indeed.

Resources