I have already wrote a class in ruby, call it Foo. Now I want to create some instance of it. However, I want to use the value stored in a variable as the name of the instance.
For example, the value of a variable bar is "ABC". Now I want to make the name of the new instance "ABC" as the statement ABC = Foo.new
Can I realize this? If it is possible, please tell me how. Thanks!!!
In Ruby, you should also not be afraid to use eval(), as long as you feed it only data constructed by you, the programmer, without unsafe external influence. In this case,
cocos = "ABC"
class_sym = :Foo
eval "#{cocos} = #{class_sym}.new"
Eval has bad reputation in many languages, but in Ruby, its quite comfortable and basically as fast as static code.
as a local variable, no, but you can use instance_variable_set and friends to name the created objects as instance variables.
You can also use const_set if you are really working with constants:
constant = 'ABC'
Object.const_set constant.to_sym, Foo.new
Related
Classes, their methods, their variables etc. have been a very new concept for me and also something that has been difficult for me to fully grasp.
I'm teaching myself Ruby, and this topic is frustrating so I decided to look at things differently.
I realize I've been using classes, instance variables and instance methods from day one.
Example
**String - class
"Hi my name is...." - instance of the String class.
.length - an instance method for the String class that I can call on the above instance.
"Hi my name is....".length => 17
I could create
x = String.new("Hi")
But I can also create
x = "Hi"
Both are the same
If I created a cat class
class Cat
attr_reader :name
def initialize(name)
#name = name
end
end
jinx = Cat.new("Jinx")
print jinx.name => Jinx
Here we have
Cat - class
"Jinx" - instance of the Cat class.
.name - an instance method for the Cat class.
Two questions
Is this a good way to view classes? When I get to bigger problems like creating a tic tac toe game (which I am doing now) with multiple classes I feel over my head at times. It's frustrating, cause I feel stupid (maybe I should get used to that), but I'm beating myself up here. I've had a very easy time going on code wars and solving a ton of problems with a single method. I know somewhere if I can relate that to classes, I'd better understand. Is looking at the solution code when stuck the best way to teach myself? I don't have anyone to ask and I want to retain as much info as possible.
Second, how does Ruby know a String is a String, an Array an Array, a Hash a Hash so I can define those variables and not call .new like I would for Cat? Is it just the syntax "" for string, [] for array, {} for hash so it assumes that's what we are talking about?
The concept you're looking for is Literals, but to explain it in more detail, let's start with your example code.
String.new
There are actually two different strings in this code:
x = String.new("Hi")
"Hi" is a string object, and then a second string object is created and stored in x. You can see this better with this equivalent code:
y = "Hi"
x = String.new(y)
The x and y variables hold two different strings objects, even though both objects contain the same contents. You can prove that they are different by modifying one of them, and checking that the other one stays the same
y.downcase!
y #=> "hi"
x #=> "Hi"
Cat.new
Considering this code:
jinx = Cat.new("Jinx")
When you say that "Jinx" is a Cat, that's not quite right. "Jinx" is still a string. Cat.new will create a new Cat object, and it will store the "Jinx" string inside the object.
You can see the difference more clearly like this:
# this is a string
name = "Jinx"
name.length #=> 4
# this is a Cat, with a string inside it
jinx = Cat.new(name)
jinx.name #=> "Jinx"
jinx.name.length => 4
The Difference
The difference between the two is that Ruby has string literals but not Cat literals.
A literal is an object that you can type into code, without actually creating it with new. Here are some examples of literals:
"Hello" makes a String object
5 makes an Integer object
5.5 makes a Float object
[] makes an Array object
{} makes a Hash object
You can see a full list of Ruby literals here: https://ruby-doc.org/core-2.3.0/doc/syntax/literals_rdoc.html
There is no literal for Cat objects, because it's a custom class you created youself. I can't just type in something like <Jinx> to create a Cat object because Ruby will not understand that -- I have to create it with Cat.new.
Don't beat yourself up over it. I know it feels overwhelming. It happens. It happens more when you teach yourself and don't have someone else who knows it to help you. Programming is tricky, it's just the nature of the beast.
I don't know Ruby, but the great thing about programming is it's only about 15% knowing the language and a lot of the thought process of coding (and a lot more debugging) so I don't need to to help you here. It knows what you are using based on the syntax of what you declare with, or the symbols like "...", [ ], and { }
I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.
I am wanting to clarify if it is not possible to declare types in Ruby or is it just not necessary? If someone wanted to declare datatypes would it be possible.
Update: My point in asking is to understand if providing a static type for variables that won't change type will provide a performance increase, in theory.
Some languages as C or Java use “strong” or “static” variable typing. Ruby is a “dynamically typed” language aka "duck typing", which means that variable dynamically changes its own type when type of assigned data has changed.
So, you can't declare variable to some strict type, it will always be dynamic.
What are you trying to do?
You can create your own class:
class Boat
end
If you want an easy way to make a class for holding data, use a struct:
class Boat < Struct.new(:name, :speed)
end
b = Boat.new "Martha", 31
You can NOT declare the class of a variable or method argument, like you can in C. Instead, you can check the type at run time:
b.is_a?(Boat) # Includes subclasses of Boat
b.class == Boat
One proposal to add typing to Ruby is http://bugs.ruby-lang.org/issues/5583 by Yasushi Ando (of parse.y famtour fame). My favorite comment was:
(b) not sure how it can honor duck typing. i think the whole idea is
to (optionally) roast the duck!
If I correctly understood what you mean with type, each Class in Ruby defines a type.
1.class
# => Fixnum
You can create a class to define a custom type
class Book
end
b = Book.new
b.class
# => Book
In Ruby, you can easily access local variables programmatically by using local_variables and eval. I would really like to have meta-programming access to these variables using a single method call such as
# define a variable in this scope, such as
x = 5
Foo.explore_locals # inside the Foo#explore_locals method, access x
where Foo is some external module. The idea is to display and export local variables in a nice way.
What should be inside the explore_locals method? Is there any way to make this possible? If absolutely necessary, I guess it could be
Foo.explore_locals binding
but this is much less elegant for the application I have in mind.
It's a shame there isn't a built-in way to get the caller's binding. The block trick seems to be the usual answer to this question.
However there is another 'trick' which existed for older 1.8 Ruby versions called binding_of_caller. Looks like quix ported it to 1.9. You might want to check that out:
https://github.com/quix/binding_of_caller
Here is an example (but it requires extra braces {} which I would rather avoid if possible):
module Foo
def self.explore_locals &block
p block.binding.eval 'local_variables'
end
end
local_1 = 3
Foo.explore_locals{} # shows [:local_1, :_]
I have a string, which has been created at runtime. I want to use this string as a variable to store some data into it. How can I convert the string into a variable name?
If you can forgive an # sign in front of the variable name, the following will work:
variable_name = ... # determine user-given variable name
instance_variable_set("##{variable_name}", :something)
This will create a variable named #whatever, with its value set to :something. The :something, clearly, could be anything you want. This appears to work in global scope, by declaring a spontaneous Object instance which binds everything (I cannot find a reference for this).
The instance_variable_get method will let you retrieve a value by name in the same manner.
instance_variable_get("##{variable_name}")
You can use eval() for this provided that you've declared your variable first:
>> foo = []
>> eval("foo")[1] = "bar"
>> foo[1]
=> "bar"
Here are the docs.
Rather than do that directly, why not consider using a hash instead. The string would be the key, and the value you want to store would be the value. Something like this:
string_values = { }
some_string = params[:some_string] # parameter was, say "Hello"
string_values[some_string] = 42
string_values # { 'Hello' => 42 }
some_number = string_values[some_string]
some_number # 42
This has a couple of benefits. First, it means you're not doing anything magic that might be hard to figure out later. Second, you're using a very common Ruby idiom that's used for similar functionality throughout Rails.
Now simply using instance_variable_set method, you can create a instance variable at runtime.
instance_variable_set('#' + 'users', User.all)
I don't mean to be negative, but tread carefully. Ruby gives you a lot of features for highly dynamic programming such as define_method, storing blocks as Proc objects to be called later, etc. Generally these are cleaner code and far safer. 99% of the time using eval() is a mistake.
And absolutely never use eval() on a string that contains user submitted input.
As Jason Watkins says, a lot of people might be quick to use eval() first thing and this would be a serious mistake. Most of the time you can use the technique described by Tack if you've taken care to use a class instance variable instead of a local one.
Local variables are generally not retrievable. Anything with the # or ## prefix is easily retrieved.