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

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

Related

ruby: calling a instance method without using instance

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.

Check if a namedspaced class exists

I'm writing a helper to check if a given class exists.
def safe_constant(constant_sym)
return Object.const_get(constant_sym) if Object.const_defined? constant_sym
end
This works just fine for something like safe_constant(:User)
However, we have a number of objects that are namespaced in modules
safe_constant(:Admin::User) is obviously not a valid symbol. I know I could determine if this exists by doing:
Object.const_get(:Admin).const_get(:User) but before I go writing some kind of split loop drill-down method to get the final constant, is there any standard way to handling this already?
According to Ruby API document, it's possible to get const_defined? and const_get working with namespace string to check and return the class constant. What needs to be done is passing the second argument (inherit) as false, for example:
class User; end
module Admin
class User; end
end
Object.const_defined?('Admin::User', false) # => true
Object.const_get('Admin::User', false) # => Admin::User
PS: const_defined? and const_get do not accept namespaced Symbol and will raise NameError exception, for example:
# namespaced String
Object.const_defined?('Admin::User', false) # => true
# namespaced Symbol
Object.const_defined?(:'Admin::User', false)
# => NameError (wrong constant name Admin::User)
How about something like this?
def safe_constant(constant_sym)
return constant_sym.to_s.constantize if valid_constant? constant_sym.to_s
end
def valid_constant?(const)
const.constantize
return true
rescue NameError
return false
end

Issue loading classes order EDIT: works, although some odd behavior along the way

I'm working on a project to recreate some of the functionality of ActiveRecord. Here's the portion that isn't working
module Associations
def belongs_to(name, params)
self.class.send(:define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
self.class.send(:define_method, :other_table_name) do |other_class|
other_class.table_name
end
.
.
.
o_c = other_class(name, params)
#puts this and other (working) values in a query
query = <<-SQL
...
SQL
#sends it off with db.execute(query)...
I'm building towards this testing file:
require 'all_files' #holds SQLClass & others
pets_db_file_name = File.expand_path(File.join(File.dirname(__FILE__), "pets.db"))
DBConnection.open(pets_db_file_name)
#class Person
#end
class Pet < SQLClass
set_table_name("pets")
set_attrs(:id, :name, :owner_id)
belongs_to :person, :class_name => "Person", :primary_key => :id, :foreign_key => :owner_id
end
class Person < SQLClass
set_table_name("people")
set_attrs(:id, :name)
has_many :pets, :foreign_key => :owner_id
end
.
.
.
Without any changes I received
.../active_support/inflector/methods.rb:230:in `block in constantize': uninitialized constant Person (NameError)
Just to make sure that it was an issue with the order of loading the classes in the file I began the file with the empty Person class, which, as predicted gave me
undefined method `table_name' for Person:Class (NoMethodError)
Since this is a learning project I don't want to change the test to make my code work (open all the classes, set all the tables/attributes then reopen them them for belongs_to. But, I'm stuck on how else to proceed.)
EDIT SQLClass:
class SQLClass < AssignmentClass
extend SearchMod
extend Associations
def self.set_table_name(table_name)
#table_name = table_name
end
def self.table_name
#table_name
end
#some more methods for finding rows, and creating new rows in existing tables
And the relevant part of AssignmentClass uses send on attr_accessor to give functionality to set_attrs and makes sure that before you initialize a new instance of a class all the names match what was set using set_attrs.
This highlights an important difference between dynamic, interpreted Ruby (et al) and static, compiled languages like Java/C#/C++. In Java, the compiler runs over all your source files, finds all the class/method definitions, and matches them up with usages. Ruby doesn't work like this -- a class "comes into existence" after executing its class block. Before that, the Ruby interpreter doesn't know anything about it.
In your test file, you define Pet first. Within the definition of Pet, you have belongs_to :person. belongs_to does :person.constantize, attempting to get the class object for Person. But Person doesn't exist yet! Its definition comes later in the test file.
There are a couple ways I can think that you could try to resolve this:
One would be to do what Rails does: define each class in its own file, and make the file names conform to some convention. Override constant_missing, and make it automatically load the file which defines the missing class. This will make load order problems resolve themselves automatically.
Another solution would be to make belongs_to lazy. Rather than looking up the Person class object immediately, it could just record the fact that there is an association between Pet and Person. When someone tries to call pet.person, use a missing_method hook to actually define the method. (Presumably, by that time all the class definitions will have been executed.)
Another way would be do something like:
define_method(belongs_to) do
belongs_to_class = belongs_to.constantize
self.class.send(:define_method, belongs_to) do
# put actual definition here
end
self.send(belongs_to)
end
This code is not tested, it's just to give you an idea! Though it's a pretty mind-bending idea, perhaps. Basically, you define a method which redefines itself the first time it is called. Just like using method_missing, this allows you to delay the class lookup until the first time the method is actually used.
If I can say one more thing: though you say you don't want to "overload" method_missing, I don't think that's as much of a problem as you think. It's just a matter of extracting code into helper methods to keep the definition of method_missing manageable. Maybe something like:
def method_missing(name,*a,&b)
if has_belongs_to_association?(name)
invoke_belongs_to_association(name,a,b)
elsif has_has_many_association?(name)
invoke_has_many_association(name,a,b)
# more...
else
super
end
end
Progress! Inspired by Alex D's suggestion to use method_missing to delay the creation I instead used define_methodto create a method for the name, like so:
define_method, :other_class) do |name, params|
(params[:class_name] || name.camelize).constantize
end
define_method(:other_table_name) do |other_class|
other_class.table_name
end
#etc
define_method(name) do #|params| turns out I didn't need to pass in `params` at all but:
#p "---#{params} (This is line 31: when testing this out I got the strangest error
#.rb:31:in `block in belongs_to': wrong number of arguments (0 for 1) (ArgumentError)
#if anyone can explain this I would be grateful.
#I had declared an #params class instance variable and a getter for it,
#but nothing that should make params require an argument
f_k = foreign_key(name, params)
p f_k
o_c = other_class(name, params)
o_t_n = other_table_name(o_c)
p_k = primary_key(params)
query = <<-SQL
SELECT *
FROM #{o_t_n}
WHERE #{p_k} = ?
SQL
row = DBConnection.execute(query, self.send(f_k))
o_c.parse_all(row)
end

Confusion about ways to use JSON in ruby sinatra application

I'm making a Ruby Sinatra application that uses mongomapper and most of my responses will be in the JSON form.
Confusion
Now I've come across a number of different things that have to do with JSON.
The Std-lib 1.9.3 JSON class: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/json/rdoc/JSON.html
The JSON Gem: http://flori.github.io/json/
ActiveSupport JSON http://api.rubyonrails.org/classes/ActiveSupport/JSON.html because I'm using MongoMapper which uses ActiveSupport.
What works
I'm using a single method to handle responses:
def handleResponse(data, haml_path, haml_locals)
case true
when request.accept.include?("application/json") #JSON requested
return data.to_json
when request.accept.include?("text/html") #HTML requested
return haml(haml_path.to_sym, :locals => haml_locals, :layout => !request.xhr?)
else # Unknown/unsupported type requested
return 406 # Not acceptable
end
end
the line:
return data.to_json
works when data is an instance of one of my MongoMapper model classes:
class DeviceType
include MongoMapper::Document
plugin MongoMapper::Plugins::IdentityMap
connection Mongo::Connection.new($_DB_SERVER_CNC)
set_database_name $_DB_NAME
key :name, String, :required => true, :unique => true
timestamps!
end
I suspect in this case the to_json method comes somewhere from ActiveSupport and is further implemented in the mongomapper framework.
What doesn't work
I'm using the same method to handle errors too. The error class I'm using is one of my own:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
attr_reader :action # :create, :update, :read or :delete
attr_reader :model # Model class
attr_reader :entity # Entity on which the error occured, or an ID for which no entity was found.
def initialize(action, model, entity = nil)
#action = action
#model = model
#entity = entity
end
end
Of course, when calling to_json on an instance of this class, it doesn't work. Not in a way that makes perfect sense: apparantly this method is actually defined. I've no clue where it would come from. From the stack trace, apparently it is activesupport:
Unexpected error while processing request: object references itself
object references itself
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:75:in `check_for_circular_references'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:46:in `encode'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `block in encode_json'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `each'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `map'
/home/id833541/.rvm/gems/ruby-1.9.3-p392/gems/activesupport-3.2.13/lib/active_support/json/encoding.rb:246:in `encode_json'
But where is this method actually defined in my class?
The question
I will need to override the method in my class like this:
# Superclass for all CRUD errors on a specific entity.
class EntityCrudError < StandardError
def to_json
#fields to json
end
end
But I don't know how to proceed. Given the 3 ways mentioned at the top, what's the best option for me?
As it turned out, I didn't need to do anything special.
I had not suspected this soon enough, but the problem is this:
class EntityCrudError < StandardError
..
attr_reader :model # Model class
..
end
This field contains the effective model class:
class DeviceType
..
..
end
And this let to circular references. I now replaced this with just the class name, which will do for my purposes. Now to_json doesn't complain anymore and I'm happy too :)
I'm still wondering what's the difference between all these JSON implementations though.

How to retrieve a class name?

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

Resources