In Ruby 1.8.6, I could write class PerformableMethod < Struct.new(:object, :method, :args)
Now in Ruby 1.9.3, that throws an error: superclass mismatch for class PerformableMethod
It works if I change the code to:
class PerformableMethod
attr_accessor :object, :method_name, :args
But why doesn't the struct work?
The class name is optional also in 1.9 and 2.0. The problem is this:
> Struct.new(:asdf, :qwer) == Struct.new(:asdf, :qwer)
=> false
Even if you provide a class name for your Struct:
> Struct.new("Zxcv", :asdf, :qwer) == Struct.new("Zxcv", :asdf, :qwer)
(irb):22: warning: redefining constant Struct::Zxcv
=> false
This means that if you have this in a file that you load or require:
class MyClass < Struct.new(:qwer, :asdf)
def some_method
puts "blah"
end
end
... then if you load it again -- maybe because you changed something and you want to try it without restarting irb, or maybe you are running Rails in dev mode and it reloads classes on each request -- then you get the exception:
TypeError: superclass mismatch for class MyClass
... because every time your class definition runs, it is declaring a brand new Struct as the superclass of MyClass. Providing a class name to Struct.new() does not help, as seen in the second code block; That just adds a warning about redefining a constant, and then opening the class fails anyway.
The only way to avoid the exception is to stash your Struct in a constant somewhere you control and make sure not to change that constant when the file is reloaded.
While typing out this question, my colleague sitting right next to me figured it out.
Struct now takes the class name as its first parameter.
So in Ruby 1.9.3 the following works:
class << Struct.new('PerformableMethod', :object, :method, :args)
Related
It seems I don't quite understand initializing or using a class within another class.
I have a Sinatra app and have created a class to handle fetching data from an api
# path: ./lib/api/bikes/bike_check
class BikeCheck
def self.check_frame_number(argument)
# logic here
end
end
BikeCheck.new
I then have another class that needs to consume/use the check_frame_number method
require 'slack-ruby-bot'
# Class that calls BikeCheck api
require './lib/api/bikes/bike_check'
class BikeCommands < SlackRubyBot::Bot
match /^Is this bike stolen (?<frame_number>\w*)\?$/ do |client, data, match|
check_frame_number(match[:frame_number])
client.say(channel: data.channel, text: #message)
end
end
BikeCommands.run
When check_frame_number is called I get a undefined method error. What I would like to know is what basic thing am I not doing/understanding, I thought by requiring the file which has the class it would be available to use.
No, you can not require a method defined in class - methods defined in class only available to class, class instances and within the inheritance.
Mixing method only possible with including modules.
To solve you issue you could either do
class BikeCommands < SlackRubyBot::Bot
match /^Is this bike stolen (?<frame_number>\w*)\?$/ do |client, data, match|
BikeCheck.check_frame_number(match[:frame_number]) # <===========
client.say(channel: data.channel, text: #message)
end
end
or write a module with the method and include/extend in class, you want that method to be available in.
I know in ruby, when we call an instance method, we need to firstly instantiate a class object.
But when I see a open sourced code I got confused.
The code is like this:
File Message.rb
require 'json'
module Yora
module Message
def serialize(msg)
JSON.generate(msg)
end
def deserialize(raw, symbolized_key = true)
msg = JSON.parse(raw, create_additions: true)
if symbolized_key
Hash[msg.map { |k, v| [k.to_sym, v] }]
else
msg
end
end
end
end
File. Persistance.rb
require 'fileutils'
require_relative 'message'
module Yora
module Persistence
class SimpleFile
include Message
def initialize(node_id, node_address)
#node_id, #node_address = node_id, node_address
FileUtils.mkdir_p "data/#{node_id}"
#log_path = "data/#{node_id}/log.txt"
#metadata_path = "data/#{node_id}/metadata.txt"
#snapshot_path = "data/#{node_id}/snapshot.txt"
end
def read_metadata
metadata = {
current_term: 0,
voted_for: nil,
cluster: { #node_id => #node_address }
}
if File.exist?(#metadata_path)
metadata = deserialize(File.read(#metadata_path)) #<============
end
$stderr.puts "-- metadata = #{metadata}"
metadata
end
.....
You can see the line I marked with "<==="
It uses deserialize function that been defined in message class.
And from message class we can see that method is a instance method, not class method.
So why can we call it without instantiating anything like this?
thanks
Message ist an module. Your Class SimpleFile includes this module. so the module methods included in your class SimpleFile. that means, all module methods can now be used like as methods from SimpleFile
see http://ruby-doc.org/core-2.2.0/Module.html for more infos about module in ruby. it's a great feature.
It is being called on an instance. In Ruby, if you leave out the explicit receiver of the message send, an implicit receiver of self is assumed. So, deserialize is being called on an instance, namely self.
Note that this exact same phenomenon also occurs in other places in your code, much earlier (in line 1, in fact):
require 'fileutils'
require_relative 'message'
Here, you also have two method calls without an explicit receiver, which means that the implicit receiver is self.
Information on what's going on here in ruby: http://coderrr.wordpress.com/2008/03/11/constant-name-resolution-in-ruby/
Doesn't help me solve my problem.. but it at least explains they 'why'
I've written the following method:
# delegate to a user permission serializer specific to the given object
# - if a serializer is not found for the given object, check the superclass
#
# #raise [NameError] if none of object, or it's superclasses have a known
# user permission serializer
# #return [UserPermission::*Serializer] returns serialized object
def self.serialized_for(object, user, klass: nil, recursion_limit: 5)
object_class = klass ? klass : object.class
# use demodulize to chop off the namespace and get the generic object name
object_name = object_class.name.demodulize
# bulid serializer name
name = "::UserPermission::#{object_name}Serializer"
begin
binding.pry
permissions = object.user_permissions(user)
return name.constantize.new(permissions)
rescue NameError => e
raise e if recursion_limit < 1
# try with super class
UserPermission.serialized_for(
object,
user,
klass: object_class.superclass,
recursion_limit: recursion_limit - 1
)
end
end
The goal is to be able to retrieve the serializer of any subclass, provided the subclass has a superclass with a serializer already defined. (I'm using ActiveModelSerializers, but that's not important here).
My problem is that I'm receiving a non-namespaced class when name.constantize runs.
My existing classes:
UserPermission
UserPermission::ProposalSerializer
PresentationSerializer < ActiveModel::Serializer
Presentation < Proposal
Proposal < ActiveRecord::Base
What I'm expecting to happen, is that when I call UserPermission.serialized_for with a Presentation, that name.constantize tries to give me a ::UserPermission::PresentationSerializer and then throw a NameError because the class doesn't exist.
What I'm getting instead is ::PresentationSerializer, which is no good - used for a different purpose.
Here is what I came up with for replicating the issue in irb:
(maybe the above context is an overly complicated explanation of this):
class NameSpace; end
class NameSpace::Klass; end
class Klass; end
class SubKlass < Klass; end
Object.const_get "::NameSpace::SubKlass"
=> SubKlass
Object.const_get("::NameSpace").const_get("SubKlass")
=> SubKlass
eval("NameSpace::SubKlass")
(eval):1: warning: toplevel constant SubKlass referenced by NameSpace::SubKlass
=> SubKlass
Is there a way I can constantize "::NameSpace::SubKlass" such that I get a NameError due to NameSpace::SubKlass not existing?
P.S.: I hope the context helps.
Edit: found another problem:
UserPermission::Template < UserPermission::Proposal
UserPermission::Template.superclass
=> Proposal
should be UserPermission::Proposal
UserPermission::Proposal
(pry):9: warning: toplevel constant Proposal referenced by UserPermission::Proposal
=> Proposal
UserPermission::Proposal is a class. So... this is a big problem. o.o
I'm using Ruby 2.1.0
Do not define your classes and modules the short-hand way. You run into scoping issues.
module UserPermission
class Proposal
end
end
module UserPermission
class Template < Proposal
end
end
UserPermission::Template.superclass
# => UserPermission::Proposal
If I run this simple Ruby code regularly, it works fine:
class String
def add_two
self + "2"
end
end
puts "hello".add_two
It prints "hello2" as it should. But this fails:
:ruby
class String
def add_two
self + "2"
end
end
puts "hello".add_two
This code produces an error:
NoMethodError at /
undefined method `add_two' for "hello":String
Any ideas what's wrong?
(Not sure if it matters, but I'm using HAML with Sinatra, which is running on Apache with the Passenger module.)
I would suggest that String is in another namespace and therefor another class.
What happens with that?
class ::String
I put your code as is into one of my Haml views in a Rails app and I got a different error to you:
SyntaxError at /
class definition in method body
So I wondered whether it was Haml's :ruby filter that was complaining, but since it "Parses the filtered text with the normal Ruby interpreter", it seemed unlikely. So, I searched for more info about the error and found references (see below) that led me to this, which works (but, really, should never be used):
:ruby
String.module_eval do
def add_two
self + "2"
end
end
puts "hello".add_two
References:
Class inside a Method Body
Class (Re)definition in Method Body
Having a string with the module and name of a class, like:
"Admin::MetaDatasController"
how do I get the actual class?
The following code works if there's no module:
Kernel.const_get("MetaDatasController")
but it breaks with the module:
ruby-1.8.7-p174 > Kernel.const_get("Admin::MetaDatasController")
NameError: wrong constant name Admin::MetaDatasController
from (irb):34:in `const_get'
from (irb):34
ruby-1.8.7-p174 >
If you want something simple that handles just your special case you can write
Object.const_get("Admin").const_get("MetaDatasController")
But if you want something more general, split the string on :: and resolve the names one after the other:
def class_from_string(str)
str.split('::').inject(Object) do |mod, class_name|
mod.const_get(class_name)
end
end
the_class = class_from_string("Admin::MetaDatasController")
On the first iteration Object is asked for the constant Admin and returns the Admin module or class, then on the second iteration that module or class is asked for the constant MetaDatasController, and returns that class. Since there are no more components that class is returned from the method (if there had been more components it would have iterated until it found the last).
ActiveSupport provides a method called constantize, which will do this. If you are on Rails, which I assume you are based on the name of your constant, then you already have ActiveSupport loaded.
require 'active_support/core_ext/string'
class Admin
class MetaDatasController
end
end
"Admin::MetaDatasController".constantize # => Admin::MetaDatasController
To see how the method is implemented, check out https://github.com/rails/rails/blob/85c2141fe3d7edb636a0b5e1d203f05c70db39dc/activesupport/lib/active_support/inflector/methods.rb#L230-L253
In Ruby 2.x, you can just do this:
Object.const_get('Admin::MetaDatasController')
=> Admin::MetaDatasController
i could be way off-base, but wouldn't eval return the class?
eval("Admin::MetaDatasController")
so eval("Admin::MetaDatasController").new would be the same as Admin::MetaDatasController.new