I want to create a function to set the instance variable like attr_reader
class Base
def exec
# get all functions to check
# if all functions return true
# I will do something here
end
end
And then I have a class inherit Base.
class SomeClass < Base
check :check_1
check :check_2
def check_1
# checking
end
def check_2
# checking
end
end
class Some2Class < Base
check :check_3
check :check_4
def check_3
# checking
end
def check_4
# checking
end
end
Because I only need 1 logic for executing in all classes but I have a lot the different checks for each class, I need to do it flexibly.
Please, give me a keyword for it.
Many thanks.
In order to have check :check_1 you need to define check as a class method:
class Base
def self.check(name)
# ...
end
end
Since you want to call the passed method names later on, I'd store them in an array: (provided by another class method checks)
class Base
def self.checks
#checks ||= []
end
def self.check(name)
checks << name
end
end
This already gives you:
SomeClass.checks
#=> [:check_1, :check_2]
Some2Class.checks
#=> [:check_3, :check_4]
Now you can traverse this array from within exec and invoke each method via send. You can use all? to check whether all of them return a truthy result:
class Base
# ...
def exec
if self.class.checks.all? { |name| send(name) }
# do something
end
end
end
SomeClass.new.exec # doesn't do anything yet
The self.class part is needed because you are calling the class method checks from the instance method exec.
Related
as a new Rubyist, I'm running into a recurring problem when it comes to structure my models.
When a method is too long:
I try to refactor to a better/shorter syntax
I try to split some parts into "sub methods"
PROBLEM: I don't know how to split the method properly + whith which tool (private method, modules etc.)
For example:
I need to run Foo.main_class_method
My model looks like this:
class Foo < Applicationrecord
def self.main_class_method
[...] # way too long method with nasty iterations
end
end
I try to split my method to improve lisibility. It becomes :
class Foo < Applicationrecord
def self.main_class_method
[...] # fewer code
self.first_splitted_class_method
self.second_splitted_class_method
end
private
def self.first_splitted_class_method
[...] # some code
end
def self.second_splitted_class_method
[...] # some code
end
end
Result: It works, but I fell like this is not the proper way to do it + I have side effects
expected: splitted_methods are not accessible, except inside main_class_method
got: I can call Foo.first_splitted_class_method since class methods "ignore" Private. splitted_class_methods under Private are not private
Question: Is it an acceptable way to split main_class_method or is it a complete misuse of private method ?
Using private method to split your code:
Possible but not the real solution if the code belongs somewhere else
It's rather about "does it belongs here?" than "does it look nicer?"
To fix the "not private" private class method (original post) :
use private_class_method :your_method_name after you defined it
or right before
private_class_method def your_method_name
[...] # your code
end
If your splitting a class/instance method:
the splitted_method must be the same type(class/instance) as the main_class_method calling it
In the main_method you can call the splitted_method with or without using self.method syntax
class Foo < Applicationrecord
def self.main_class_method
# Here, self == Foo class
# first_splitted_class == class method, I can call self.first_splitted_class_method
self.first_splitted_class_method
# I can also call directly without self because self is implicit
second_splitted_class_method
end
def self.first_splitted_class_method
end
def self.second_splitted_class_method
end
private_class_method :first_splitted_class_method, :second_splitted_class_method
end
I use a gem to manage certain attributes of a gmail api integration, and I'm pretty happy with the way it works.
I want to add some local methods to act on the Gmail::Message class that is used in that gem.
i.e. I want to do something like this.
models/GmailMessage.rb
class GmailMessage < Gmail::Message
def initialize(gmail)
#create a Gmail::Message instance as a GmailMessage instance
self = gmail
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
I don't want to persist it. But obviously I can't define self in that way.
To clarify, I want to take an instance of Gmail::Message and create a GmailMessage instance which is a straight copy of that other message.
I can then run methods like #gmail.subject and #gmail.html, but also run #gmail.something_clever... and save local attributes if necessary.
Am I completely crazy?
You can use concept of mixin, wherein you include a Module in another class to enhance it with additional functions.
Here is how to do it. To create a complete working example, I have created modules that resemble what you may have in your code base.
# Assumed to be present in 3rd party gem, dummy implementation used for demonstration
module Gmail
class Message
def initialize
#some_var = "there"
end
def subject
"Hi"
end
end
end
# Your code
module GmailMessage
# You can code this method assuming as if it is an instance method
# of Gmail::Message. Once we include this module in that class, it
# will be able to call instance methods and access instance variables.
def something_clever
puts "Subject is #{subject} and #some_var = #{#some_var}"
end
end
# Enhance 3rd party class with your code by including your module
Gmail::Message.include(GmailMessage)
# Below gmail object will actually be obtained by reading the user inbox
# Lets create it explicitly for demonstration purposes.
gmail = Gmail::Message.new
# Method can access methods and instance variables of gmail object
p gmail.something_clever
#=> Subject is Hi and #some_var = there
# You can call the methods of original class as well on same object
p gmail.subject
#=> "Hi"
Following should work:
class GmailMessage < Gmail::Message
def initialize(extra)
super
# some additional stuff
#extra = extra
end
def something_clever
#do something clever utilising the Gmail::Message methods
end
end
GmailMessage.new # => will call first the initializer of Gmail::Message class..
Building upon what the other posters have said, you can use built-in class SimpleDelegator in ruby to wrap an existing message:
require 'delegate'
class MyMessage < SimpleDelegator
def my_clever_method
some_method_on_the_original_message + "woohoo"
end
end
class OriginalMessage
def some_method_on_the_original_message
"hey"
end
def another_original_method
"zoink"
end
end
original = OriginalMessage.new
wrapper = MyMessage.new(original)
puts wrapper.my_clever_method
# => "heywoohoo"
puts wrapper.another_original_method
# => "zoink"
As you can see, the wrapper automatically forwards method calls to the wrapped object.
I'm not sure why you can't just have a simple wrapper class...
class GmailMessage
def initialize(message)
#message = message
end
def something_clever
# do something clever here
end
def method_missing(m, *args, &block)
if #message.class.instance_methods.include?(m)
#message.send(m, *args, &block)
else
super
end
end
end
Then you can do...
#my_message = GmailMessage.new(#original_message)
#my_message will correctly respond to all the methods that were supported with #original_message and you can add your own methods to the class.
EDIT - changed thanks to #jeeper's observations in the comments
It's not the prettiest, but it works...
class GmailMessage < Gmail::Message
def initialize(message)
message.instance_variables.each do |variable|
self.instance_variable_set(
variable,
message.instance_variable_get(variable)
)
end
end
def something_clever
# do something clever here
end
end
Thanks for all your help guys.
I'm trying to create a module that will be included in many different classes. It needs to record the caller's path to the class file
so I can reference the path in later code. This code tries to add a method to the calling class, but fails because it just returns the current value of ##x.
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
##x, = caller[0].partition(":") # this works ##x = "/home/eric/FindMe.rb"
obj.class_eval do
def my_class_file_path
return ????? # I want to return "/home/eric/FindMe.rb"
end
end
end
end
The GladeGUI module will be "included" in many different classes, so I can't just add code to the calling class. I need a way to make ##x compile into a constant value, so the method stored in the class looks like this:
def my_class_file_path
return "/home/eric/FindMe.rb"
end
How do I convert a variable to a constant in code?
Thanks.
It seems like you don't actually need it to be a "constant" - you just need some way to make the method return the correct value all the time and not allow other code to come along and change the value (with the current ##x solution, someone can just modify ##x and it will break)
The solution is to store the data in a local variable instead of a class or instance variable, and then access that local variable via a closure.
No other code will have scope to 'see' the local variable and thus it cannot be changed.
But then the problem becomes that when you use def inside a class_eval, the scope of the caller isn't captured, so the code can't see your local variable. You can use define_method instead
Here's an example
# /home/eric/GladeGUI.rb
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_method :my_class_file_path do
return caller_file_path
end
end
end
end
# /home/eric/FindMe.rb
class FindMe
include GladeGUI
end
puts FindMe.new.my_class_file_path # prints the correct path
But - what if you want my_class_file_path to be a class method rather than an instance method - use define_singleton_method instead:
module GladeGUI
def self.included(obj)
caller_file_path = caller[0].split(":").first
obj.class_eval do
define_singleton_method :my_class_file_path do
return caller_file_path
end
end
end
end
...
puts FindMe.my_class_file_path
Interesting side note: This is how you can fake "private variables" in javascript :-)
I am using a library that is implementing a belongs_to association between two entries in a database. Since this is not the behaviour I need I want to override this method via prepend. But pry tells me that the original method is still called. I double checked and I'm using ruby 2.0.
The code that gets prepended:
module Associations
module ClassMethods
[...]
#Add the attributeName to the belongsToAttributes
#and add a field in the list for the IDs
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
I use this in this class:
Note: I also tried to directly override the method in this class but it still doesn't happen
require 'couchbase/model'
class AdServeModel < Couchbase::Model
[...]
#I tried to add the belongs_to method like this
#def belongs_to(attr_name)
# #belongsToAttributes ||= []
# #belongstoAttributes << attr_name
# create_attr attr_name.to_s
# attribute belongs_to_string.concat(attr_name.to_s).to_sym
# end
# def belongsToAttributes
# #belongsToAttributes
# end
end
When I check with pry it shows me that I end up in this method call:
def self.belongs_to(name, options = {})
ref = "#{name}_id"
attribute(ref)
assoc = name.to_s.camelize.constantize
define_method(name) do
assoc.find(self.send(ref))
end
end
Any pointer to what I'm doing wrong would be appreciated.
Edit:
Ok I solved the problem like this:
self.prepended(base)
class << base
prepend ClassMethods
end
end
end
# prepend the extension
Couchbase::Model.send(:prepend, Associations)
Since Arie Shaw's post contains important pointers to solve this problem I will accept his answer. Although he missed the point about extending and prepending the method that I want to call. For a more detailed discussion about my trouble with prepending the methods please refer to this question.
According to the pry trace you posted, the method you wanted to monkey patch is a class method of AdServeModel, not a instance method.
The problem with your Associations module approach is, you are calling Module#prepend to prepend the module to the existing class, however, you wrote a self.included hook method which will only be called when the module is included (not prepended). You should write Module#prepended hook instead.
The problem with the directly overriding approach is, you were actually overriding the instance method, rather than the class method. It should be something like this:
require 'couchbase/model'
class AdServeModel < Couchbase::Model
class << self
# save the original method for future use, if necessary
alias_method :orig_belongs_to, :belongs_to
def belongs_to(attr_name)
#belongsToAttributes ||= []
#belongstoAttributes << attr_name
create_attr attr_name.to_s
attribute belongs_to_string.concat(attr_name.to_s).to_sym
end
def belongsToAttributes
#belongsToAttributes
end
end
end
For example, I have this array:
tags_array=['<code>','<span>','<div>', '<label>','<a>', '<br>', '<p>' '<b>','<i>', '<del>', '<strike>', '<u>', '<img>', '<video>', '<audio>', '<iframe>', '<object>', '<embed>', '<param>', '<blockquote>', '<mark>', '<cite>', '<small>', '<ul>', '<ol>', '<li>', '<hr>', '<dl>', '<dt>', '<dd>', '<sup>', '<sub>', '<big>', '<pre>', '<code>', '<figure>', '<figcaption>', '<strong>', '<em>', '<table>', '<tr>', '<td>', '<th>', '<tbody>', '<thead>', '<tfoot>', '<h1>', '<h2>', '<h3>', '<h4>', '<h5>','<h6>']
I don't want to define it in each model method where I use it, it seems not very smart copy-pasting.
And of course I don't want separate DB row for that.
How can I define it once, so it would be visible for each model method, like instance initialize method.
Like
class MyModel<ActiveRecord::Base
#...
#tags_array=['','',...]
def onemethod
#tags_array.split!
#...
end
def twomethod
#tags_array.capitalize!
#...
end
end
You could define it as a module and mix it in.
module TagsArray
tags_array=['','',...]
def self.onemethod
tags_array.split
#...
end
def self.twomethod
tags_array.capitalize
#...
end
end
Then you can just mix it in to your Models.
class MyModel<ActiveRecord::Base
include TagsArray
end
Hope this helps.
Also you can drop the ! from the end of split and capitalize unless you want to alter the tags_array permanently each time you call the method.