How we can instantiate class from it's name string in Ruby-on-Rails?
For example we have it's name in database in format like "ClassName" or "my_super_class_name".
How we can create object from it?
Solution:
Was looking for it myself, but not found, so here it is.
Ruby-on-Rails API Method
name = "ClassName"
instance = name.constantize.new
It can be even not formatted, we can user string method .classify
name = "my_super_class"
instance = name.classify.constantize.new
Of course maybe this is not very 'Rails way', but it solves it's purpose.
klass = Object.const_get "ClassName"
about class methods
class KlassExample
def self.klass_method
puts "Hello World from Class method"
end
end
klass = Object.const_get "KlassExample"
klass.klass_method
irb(main):061:0> klass.klass_method
Hello World from Class method
Others may also be looking for an alternative that does not throw an error if it fails to find the class. safe_constantize is just that.
class MyClass
end
"my_class".classify.safe_constantize.new # #<MyClass:0x007fec3a96b8a0>
"omg_evil".classify.safe_constantize.new # nil
You can simply convert a string and initialize a class out of it by:
klass_name = "Module::ClassName"
klass_name.constantize
To initialize a new object:
klass_name.constantize.new
I hope this turns out to be helpful.
Thanks!
I'm surprised nobody is considering security and hacking in their responses. Instantiation of an arbitrary string that likely came even indirectly from user input is asking for trouble and hacking. We all should/must be whitelisting unless we're sure the string is fully controlled and monitored
def class_for(name)
{
"foo" => Foo,
"bar" => Bar,
}[name] || raise UnknownClass
end
class_for(name_wherever_this_came_from).create!(params_somehow)
How you would know the appropriate params arbitrarily without having a whitelist would be challenging but you get the idea.
Related
Why are #variables needed in classes? What value do they add? I couldn't find anything online for this but maybe I'm searching for the wrong terms. Is there a resource online I can look this up? Thanks!
car.rb
class Car
attr_accessor :make, :model
def initialize(make = '')
#make = ''
#model = ''
end
end
These variables are called instance variables. Every instance of the class has it's own copy of these variables.
In your example, you would like every instance of the class Car to have it's own make and model.
Note the following example
car1 = Car.new("Toyota", "Carola")
car2 = Car.new("Mitsubishi", "Lancer")
Both car1 and car2 each have their own private make and model. The way to tell the Ruby interpreter to do this is to use #.
Instance variables are available in all areas of each instance of a class.
Each time you create an instance of car the variable is specific to that particular one.
e.g.
car1 = Car.new('Ford', 'Falcon')
car2 = Car.new('Toyota', 'Camry')
Now car1 and car2 have different instances of #make and #model.
If you declare the variable as a class variable using ##make, then every Car has access to it and every time it is changed, it is changed for everyone.
Basically class variables allow you to put some 'walls' around your data.
The attr_accessor creates two methods in your call
def make=(value)
#make = value
end
def make
#make
end
This allows you to call the instance variable within and from outside your class without the #
e.g.
car1.make
returns
'Ford'
www.codecademy.com has some great free courses in basic Ruby that will teach you this stuff really well.
That's the syntax for defining instance variables in ruby.
Objects are said to have behaviours and properties, much like real world object.
The properties of objects are defined by instance variables. And # prefix is
ruby's way of telling particular variable is an instance variable/characteristic.
What value they add ?
They help you to have a variable that is accessible across all your instance methods.
Let us disect your example
class Car
attr_accessor :make, :model #attr_accessor methods creates a getter method. car_instance.make and
#a setter method car_instance.make=(val), which otherwise you would have to do explicitly.
def initialize(make = '') #constructor
#make = '' #initializing your instance variables. I guess you meant #make = make here
#model = ''
end
end
I couldn't find anything online for this but maybe I'm searching for the wrong terms.
Yup you were.
Is there a resource online I can look this up?
Yup, Ruby User Guide By Matz
Bit more details
In ruby, the scope of a variable inside a class is identified by the compiler by first two characters of the
varaible name.
##variable_name -> Two '#' s. Class variables/ Scope across all instances of the class. Eg: If you want to have a count on total number of cars you made.
#variable -> One '#' Instance varaibles/ Scope across an instance.
variable -> No '#' Local variables/ Scope to the block they belong.
Look at the below code,which I changed a bit for understanding:
class Car
attr_accessor :make, :model
def initialize(make = '')
#make = make
#model = ''
end
end
car1 = Car.new("Toyota")
car2 = Car.new("Roxin")
p car1.instance_variables #<~~ A
p car2.instance_variables #<~~ B
p car1 #<~~C
p car2#<~~D
Output:
[:#make, :#model]
[:#make, :#model]
#<Car:0x1bc89e8 #make="Toyota", #model="">
#<Car:0x1bc8940 #make="Roxin", #model="">
Explanation: Here in A and B you might think of that both car1,car2 are using the same copy of instance variables. But Not is the case. See when I am printing car1 and car2, in C and D,outputs shows that car1 and car2 are holding different values for the instance variable #make. This fact objects has their own unique instance variables.
Hope this helps.
Cheers!!
I am using Ruby on Rails v3.2.2 and I would like to call methods "directly" on instance attributes and not on their receiver objects. That is, if I have an User instance #user for which
#user.name
# => "Leonardo da Vinci"
I would be able to implement methods that act "directly" on the name attribute so that I can write something like
# Note: 'is_correct?' and 'has_char?' are just sample methods.
#user.name.is_correct?
#user.surname.has_char?('V')
Is it possible? If so, how can I make that?
Note: I am trying to implement a plugin.
In order to do this, you would have to use a special type of class for each attribute, which IMHO, would be hugely overkill, assuming your reasons are purely with concern for visual style.
For example, since #user.name returns a String, you can only call methods on it that belong to the String class by default. If you want to call additional methods on it, you either want to use a subclass of String, or add some singleton methods to that particular instance of String. I think it would be confusing and inconsistent and would likely get in the way of real progress.
A better solution is just to ask something like:
#user.valid?(:name)
As for has_char?('V'), you can already do that with instances of String:
#user.surname.include?('V')
I just played a lil and got it working using singleton methods in a normal class and I'm not sure if it's possible to do the same on your case. I doubt it! But still here is what I got :)
class Bar
def initialize foo
#foo = foo
def #foo.bar
p "baaaaaaar"
end
end
def foo
#foo
end
def foo=(foo)
#foo = foo
def #foo.bar
p "baaaaaaar"
end
end
end
a = Bar.new "foo"
p a.foo
p a.foo.bar
a.foo = "bar"
p a.foo.bar
# >> "foo"
# >> "baaaaaaar"
# >> "baaaaaaar"
# >> "baaaaaaar"
# >> "baaaaaaar"
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.
I have a rails model class
class Model < ActiveRecord::Base
has_many :object_collection
def add_object(object)
object_collection.push object // works
#object_collection.push object // does not work
self.object_collection.push object // works
end
end
I was wondering if someone can please explain to me why the # does not work yet self does i thought these two meant the same
cheers
They are not the same. Consider the following Ruby code:
class Person
attr_accessor :employer
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
The method attr_accessor conveniently generates an attribute reader and writer for you (employer= and employer). You can use these methods to read and write an attribute, which is stored in the instance variable #employer.
Now, we can rewrite the above to the following, which is functionally identical to the code above:
class Person
def employer=(new_employer)
#works_for = new_employer
end
def employer
#works_for
end
end
john = Person.new
john.employer = "ACME"
john.employer # equals "ACME"
Now, the instance variable #employer is no longer used. We chose to write the accessors manually, and have the freedom to pick a different name for the instance variable. In this particular example, the name of the instance variable is different than the name of the attribute accessors. There is nothing that prevents you from doing that.
This is similar to how ActiveRecord stores its attributes internally. They are not stored in instance variables of the same name, that is why your push call to #object_collection does not work.
As you may understand, attribute readers and writers offer a certain abstraction that can hide the implementation details from you. Reading and writing instance variables directly in subclasses is therefore generally considered bad practice.
#foo identifies an instance variable called #foo. foo identified a method called foo.
By default, instance variables in Ruby are private. It means you cannot access the value of an instance variable unless you have some public method that exposes the value.
Those methods are called setters and getters. By convenction, setter and getter have the same name of the instance variable, but this is not a requirement.
class MyClass
def initialize
#foo
end
def foo=(value)
#foo = foo
end
def foo
#foo
end
def an_other_foo=(value)
#foo = foo
end
def an_other_foo
#foo
end
end
Though methods and instance variables can have similar names, thery are different elements.
If this topic is not clear to you, you probably need to stop playing with Rails and go back studying how Ruby works.
In your specific case, object_collection doesn't exist as an instance variable because it's an association method.
They do not mean the same thing. One is an instance variable, the other is a method.
The #foo means "the value of the instance variable foo", where as self.foo means "the value of a call to the method foo on myself".
It is typical for a method foo= to set the #foo instance variable, so I can see how someone new to the language might be confused. I'd encourage you to pick up a book on the ruby language. There's one specifically for people who have done some rails but never learned ruby proper. You often can hack rails without understanding the language or what these statements mean, but you'll be far less productive than someone who spends the small amount of time it takes to learn the ruby language itself.
As a general rule, use the self.foo form whenever you can, as this is less sensitive to changes in the classes definition.
I tried creating a model called "class" (as in a graduating class of students), and encountered all kinds of problems. What are some other words or class names to avoid in Rails?
Some links I've found:
http://juicebar.wordpress.com/2007/05/30/reserved-words-in-rails/
http://railsforum.com/viewtopic.php?id=22242
This page has a very long list of words not to use:
https://reservedwords.herokuapp.com/words
Because 'class' comes up very commonly as a name with metaprogamming, I think the accepted ruby alternative is 'klass'. This is obviously a different context from your graduating class situation, but maybe still helpful.
You've got most of them there. Obviously, you also need to avoid the Ruby keywords:
alias and BEGIN begin break case class def defined
do else elsif END end ensure false for if
in module next nil not or redo rescue retry
return self super then true undef unless until when
while yield
(from http://www.zenspider.com/Languages/Ruby/QuickRef.html#4).
Also, don't name a model Transaction (but the generator warns about that!).
Class is a built-in ruby class. It is what classes is an instance of.
class Foo
end
puts Foo.class
# => Class
Overriding that blows up the entire object structure in Ruby.