How to retrieve a class name? - ruby

I am using Ruby on Rails 3.0.7 and I would like to retrieve the class name, also if it is namespaced. For example, if I have a class named User::Profile::Manager I would retrieve the Manager string from that using some unknown to me Ruby or Ruby on Rails method and in a secure way.
BTW: What other "usefull" information that are "commonly" used can I get for the class?

Some useful simple metaprogramming calls:
user = User::Profile::Manager.new(some_params)
user.class # => User::Profile::Manager
user.class.class # => Class
user.class.name # => "User::Profile::Manager"
user.class.name.class # => String
# respond_to? lets you know if you can call a method on an object or if the method you specify is undefined
user.respond_to?(:class) # => true
user.respond_to?(:authenticate!) # => Might be true depending on your authentication solution
user.respond_to?(:herpderp) # => false (unless you're the best programmer ever)
# class.ancestors is an array of the class names of the inheritance chain for an object
# In rails 3.1 it yields this for strings:
"string".class.ancestors.each{|anc| puts anc}
String
JSON::Ext::Generator::GeneratorMethods::String
Comparable
Object
PP::ObjectMixin
JSON::Ext::Generator::GeneratorMethods::Object
ActiveSupport::Dependencies::Loadable
Kernel
BasicObject
If you want the lowest-level class from User::Profile::Manager I'd probably do the following [using a regex for this seems like overkill to me ;)]:
user = User::Profile::Manager.new
class_as_string = user.class.name.split('::').last # => "Manager"
class_as_class = class_name.constantize # => Manager
Edit:
If you actually want to look through some more metaprogramming calls, check the docs for the Object and Module classes, and check out the google results for "Ruby Metaprogramming".

Have you tried class method:
class A
class B
end
end
myobject = A::B.new
myobject.class
=> A::B

To expand on #JCorcuera's answer, some other useful information can be found with kind_of? and methods
class A
class B
def foo
end
end
end
myobject = A::B.new
p myobject.class
=> A::B
p myobject.kind_of? A::B
=> true
p myobject.methods
=> [:foo, :nil?, :===, :=~, ...
p myobject.methods.include? :foo
=> true

Related

Mongoid embedded collection response to :find

I'm sending serialized data to a class which need to access a Mongoid document which may or may not be embedded.
In case of embedded document, I'm accepting a variable number of arguments which I reduce to get the embedded document.
The code is pretty simple:
def perform(object, *arguments)
#opts = arguments.extract_options!
#object = arguments.reduce(object){|object, args| object.public_send(*args)}
# [...]
I used public_send because AFAIK I only need to call public methods.
However, when I try to access an embedded document I have some really strange result where #object is an enumerator.
After some debugging, this is what I found that for any root document object and an embedded collection items, I have:
object.items.public_send(:find)
# => #<Enumerator: ...>
object.items.send(:find) # or __send__
# => nil
The method called is not the same at all when I call public_send or send!
How is it even possible?
Is it normal? Is that a bug?
public_send seems to invoke the find method of Array (Enumerable) but send (or __send__) invokes the find method of Mongoid
Edit: simple reproductible case:
require 'mongoid'
class User
include Mongoid::Document
field :name, type: String
embeds_many :groups
end
class Group
include Mongoid::Document
field :name, type: String
embedded_in :user
end
Mongoid.load_configuration({
sessions: {
default: {
database: 'send_find',
hosts: [
'localhost:27017'
]
}
}
})
user = User.create(name: 'john')
user.groups.create(name: 'g1')
user.groups.create(name: 'g2')
puts "public_send :find"
puts user.groups.public_send(:find).inspect
# => #<Enumerator: [#<Group _id: 5530dea57735334b69010000, name: "g1">, #<Group _id: 5530dea57735334b69020000, name: "g2">]:find>
puts "send :find"
puts user.groups.send(:find).inspect
# => nil
puts "__send__ :find"
puts user.groups.__send__(:find).inspect
# => nil
Okay, after a few hours of debugging, I found that it is actually a bug in Mongoid.
The relation is not an array but a proxy around the array, which delegates most methods to the array.
As public_send was also delegated but not send and __send__, the behavior was not the same.
For more information, see my pull request and the associated commit.

Ruby, properly error handling class constructor options

I am working on an application in Ruby, and I am trying to write a clean and efficient API and would need properly error handle options passed to a class constructor.
For instance:
class SomeClass
def initialize(options = {})
#some_opt = options[:some_opt]
#some_other_opt = options[:some_other_opt]
end
end
sc = SomeClass.new(:some_opt => 'foo', :some_other_opt => 'bar')
How would I make sure, that if the user adds an option wich the application does not accept, the application will raise an error?
sc = SomeClass.new(:some_opt => 'foo', :some_new_opt => 'foobar') # => Unknown option 'some_new_opt'
Would it be better to only use the options that you need to, and disregard any other options passed to the class?
For Ruby versions 1.8 and 1.9 disregarding the extra options is generally how that situation is handled. You can raise an error if you want, but I wouldn't recommend it:
require "set"
class SomeClass
ACCEPTED_KEYWORDS = Set[:some_opt, :some_other_opt]
def initialize(options = {})
raise ArgumentError unless options.keys.all? {|k| ACCEPTED_KEYWORDS.include? k }
# Rest of the code...
end
end
For Ruby version 2.0 and above you can use keyword arguments, and you can make them required from version 2.1 onwards.

How do I dynamically constantize the name of a namespaced class?

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

Achieving java enum like behaviour in ruby

Can I create something in my model to do something like:
MyModel::TYPE::ONE
MyModel::TYPE::TWO
where ONE and TWO are strings? I placed them in a constant in my model like:
class MyModel
TYPE = ['ONE', 'TWO']
end
so I can access MyModel::Type and get the array, but how do I get one more level?
You can get the syntax you desire with:
[~]$ irb
irb(main):001:0> module MyModel
irb(main):002:1> module TYPE
irb(main):003:2> ONE = 1
irb(main):004:2> TWO = 2
irb(main):005:2> end
irb(main):006:1> end
=> 2
irb(main):007:0> MyModel::TYPE::ONE
=> 1
irb(main):008:0> MyModel::TYPE::TWO
=> 2
This has the disadvantage, or maybe the advantage, of allowing extra "attributes" on the enum, not unlike what Java gives you. You can make the values of ONE and TWO be maps if you like, which is similar to Java's enum objects.
EDIT: You can also get the values like this:
irb(main):009:0> MyModel::TYPE::constants
=> [:ONE, :TWO]

Active Record to_json\as_json on Array of Models

First off, I am not using Rails. I am using Sinatra for this project with Active Record.
I want to be able to override either to_json or as_json on my Model class and have it define some 'default' options. For example I have the following:
class Vendor < ActiveRecord::Base
def to_json(options = {})
if options.empty?
super :only => [:id, :name]
else
super options
end
end
end
where Vendor has more attributes than just id and name. In my route I have something like the following:
#vendors = Vendor.where({})
#vendors.to_json
Here #vendors is an Array vendor objects (obviously). The returned json is, however, not invoking my to_json method and is returning all of the models attributes.
I don't really have the option of modifying the route because I am actually using a modified sinatra-rest gem (http://github.com/mikeycgto/sinatra-rest).
Any ideas on how to achieve this functionality? I could do something like the following in my sinatra-rest gem but this seems silly:
#PLURAL.collect! { |obj| obj.to_json }
Try overriding serializable_hash intead:
def serializable_hash(options = nil)
{ :id => id, :name => name }
end
More information here.
If you override as_json instead of to_json, each element in the array will format with as_json before the array is converted to JSON
I'm using the following to only expose only accessible attributes:
def as_json(options = {})
options[:only] ||= self.class.accessible_attributes.to_a
super(options)
end

Resources