This question already has answers here:
Setter method (assignment) with multiple arguments
(2 answers)
Closed 8 years ago.
My method is:
def title=(text, sub_text = 'another piece of text')
self.title = text + sub_text
end
Somewhere else in my code, I do something like:
subtext = "enhusiasts"
title = "hello ruby "
How can I pass subtext to the title setter function so that my title becomes:
hello ruby enthusiasts
Do I have to write a separate function to use this setter?
While there is nothing particularly special about writer methods ending in =, the syntax of the language will not allow them to be called with multiple arguments. You can use send:
object.send :title=, title, subtext
That is not a clean solution, though. Also, your title= method is recursive; you should be setting an instance variable directly.
I recommend something like this:
attr_writer :text, :sub_text
def title
text + sub_text
end
# ...
object.text = 'hello ruby '
object.sub_text = 'enthusiasts'
object.title
# => "hello ruby enthusiasts"
You can call it using self.send('title=', 'string1') or self.send('title=', 'string1', 'string2').
But you cannot just call it the usual way title = 'string1', 'string2'. That is because the parser wont allow it. When = is present, the statement is expected to be in identifier = expression format.
Better you don"t you name your function with the = in such case.
Also it is recommended and is the convention that you concat your string by interpolation i.e.:
self.title = "#{text} #{sub_text}" instead of self.title = text + sub_text.
This prevents error being generated when text is nil.
Related
According to ruby-doc.org, kernel#eval takes a binding object as the second argument as follows:
Const = 7
p eval("Const", binding) # => 7
The method eval can be used with an object returned by a method get_binding that accepts one parameter:
def get_binding(param)
return binding
end
n = get_binding(7)
p eval("param", n) # => 7
In the first piece of code, we get the value of Const, and in the second piece of code, we get the value of param. If we use eval and binding to retrieve values that we already know, what are these methods useful for?
For sake of discussion, lets say you are working on a template engine - which will process a given text and replace the Ruby code in it with the its value. We can use eval for that.
Being a general purpose engine, the template text should allow usage of Ruby variables, whose value will be defined in the caller's binding. In such cases, by passing binding to the eval, we can allow user's binding to be used for variable evaluation.
A rudimentary, not elegant, approach is demonstrated below:
template = "Hello #first_name# #last_name#"
def process(template, b)
vars = template.scan(/#(\w+)#/).flatten
vars.each {|v| template = template.gsub("##{v}#", eval("#{v}", b)) }
return template
end
first_name = "Wand"
last_name = "Maker"
str = process(template, binding)
p str
#=> "Hello Wand Maker"
first_name = "Rubeus"
last_name = "Hagrid"
p process(template, binding)
#=> "Hello Rubeus Hagrid"
A somewhat similar approach has been used in ERB, Ruby's in-built template engine. You can take a look at source code
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
Hi I`m learning from LRtHW and I got stuck....
I have program like this:
require 'open-uri'
WORD_URL = "http://learncodethehardway.org/words.txt"
WORDS = []
PHRASES = {
"class ### < ###\nend" => "Make a class named ### that is-a ###.",
"class ###\n\tdef initialize(###)\n\tend\nend" => "class ### has-a initialize that takes ### parameters.",
"class ###\n\tdef ***(###)\n\tend\nend" =>"class ### has-a function named *** that takes ### parameters.",
"*** = ###.new()" => "Set *** to an instance of class ###.",
"***.***(###)" => "From *** get the *** function, and call it with parameters ###.",
"***.*** = '***'" => "From *** get the *** attribute and set it to '***'."
}
PHRASE_FIRST = ARGV[0] == "english"
open(WORD_URL) do |f|
f.each_line {|word| WORDS.push(word.chomp)}
end
def craft_names(rand_words, snippet, pattern, caps=false)
names = snippet.scan(pattern).map do
word = rand_words.pop()
caps ? word.capitalize : word
end
return names * 2
end
def craft_params(rand_words,snippet,pattern)
names = (0...snippet.scan(pattern).length).map do
param_count = rand(3) + 1
params = (0...param_count).map {|x| rand_words.pop()}
params.join(', ')
end
return names * 2
end
def convert(snippet, phrase)
rand_words = WORDS.sort_by {rand}
class_names = craft_names(rand_words, snippet, /###/, caps=true)
other_names = craft_names(rand_words, snippet,/\*\*\*/)
param_names = craft_params(rand_words, snippet, /###/)
results = []
for sentence in [snippet, phrase]
#fake class name, also copies sentence
result = sentence.gsub(/###/) {|x| class_names.pop}
#fake other names
result.gsub!(/\*\*\*/) {|x| other_names.pop}
#fake parameter list
result.gsub!(/###/) {|x| param_names.pop}
results.push(result)
end
return results
end
# keep going until they hit CTRL-D
loop do
snippets = PHRASES.keys().sort_by { rand }
for snippet in snippets
phrase = PHRASES[snippet]
question, answer = convert(snippet, phrase)
if PHRASE_FIRST
question, answer = answer, question
end
print question, "\n\n> "
odp = gets.chomp
if odp == "exit"
exit(0)
end
#exit(0) unless STDIN.gets
puts "\nANSWER: %s\n\n" % answer
end
end
I understand most of this code, but I have a problem with:
for sentence in [snippet, phrase]
I know that it is a "for" loop and it creates a "sentence" variable, but how does the loop know that it need to look in a key and value of hash "PHRASES"
And my second "wall" is:
question, answer = convert(snippet, phrase)
It looks like it creates and assigns "question" and "answer variables to the "convert" method with "snippet" and "phrase" parameters... again how does it assigns "question" to a key and answer to a value.
I know that this is probably very simple but as for now it blocks my mind :(
For your first question about the for-loop:
Look at where the for-loop is defined. It's inside the convert() method, right? And the convert() method is passed two arguments: one snippet and one phrase. So the loop isn't "looking" for values in the PHRASES hash, you are the one supplying it. You're using the method's arguments.
For your second question about assignment:
In Ruby we can do something called "destructuring assignment". What this means is that we can assign an array to multiple variables, and each variable will hold one value in the array. That's what's happening in your program. The convert() method returns a two-item array, and you're giving a name (question and answer) to each item in the array.
Here's another example of a destructuring assignment:
a, b, c = [1, 2, 3]
a # => returns 1
b # => returns 2
c # returns 3
Try this out in IRB and see if you get the hang of it. Let me know if I can help clarify anything, or if I misunderstood your question. You should never feel bad about asking "simple" questions!
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
What I do need:
I pass a string that has to set an unmutable object inside an array, but I do not know hot how to make the transition from the string that the user inputs to the object name I need.
What I am intending to do:
I am working on a conversational adventure. The key point is to have a function that creates a command prompt so the user can interact with the game. Whenever the user says "go to somewhere", there is another function called "goto" that compares whether the input is included in the exits of the place where the player is; if so, the attribute "place" for the player takes a new place.
What I did:
I made a command prompt that actually works*
loop do
print "\n >>> "
input = gets.chomp
sentence = input.split
case
when sentence[0] == "inspect"
$action.inspect(sentence[1])
when sentence[0] == "go" && sentence[1] == "to"
$action.goto(sentence[2])
when sentence[0] == "quit"
break
else
puts "\nNo le entiendo Senor..."
end
And I initialized the objects as I need them (the third attribute goes for the exits):
room = Place.new("room", "Room", [newroom], "This is a blank room. You can _inspect_ the -clock- or go to [newroom].", ["this this"])
newroom = Place.new("newroom", "New Room", [room], "This is another blank room. You can _inspect_ the -clock-", ["this this"])
Then I made a method inside the action controller that has to compare and set the places properly. (Beware: monster newbie code following. Protect you eyes).
def goto(destiny) #trying to translate the commands into variable names
if (send "#{destiny}").is_in? $player.place.exits
$player.place = send "#{sentence[2]}"
puts destiny.description
else
puts "I cannot go there."
end
end
I think you want to convert a string to constant. Well it is easy. Read an example:
string = 'Hash'
const = Object.const_get(string) #=> Hash
const.new #=> {}; <- it is an empty Hash!
But be careful. If there's no such a constant you will get uninitialized constant error. In this case your adventures will stop.
I hope I understood your question and you will understand my answer.
How to change string to object, there are few options:
Bad(eval family):
eval("name_of_your_variable = #{21+21}")
eval("puts name_of_your_variable") #42
You can see that eval can make everything. So use with caution.
However, as pointed by #user2422869 you need(be in) scope - place where your variables are saved. So above code won't run everywhere
Everytime you run following method you create another scope
def meth1
puts "defined: #{(defined? local_a) ? 'yes' : 'no'}!"
eval 'local_a = 42'
local_a += 100
eval 'puts local_a'
end
meth1
and here is output:
defined: no!
142
If you want to grab local_a from one of scopes of meth1 you need binding.
def meth2
var_a = 222
binding
end
bin = meth2
bin.eval 'var_a'
#output:
#222
About binding you can read in doc. As for scopes, I don't have good site.
Better:
hash_variable = Hash.new # or just {}
hash[your_string_goes_here] = "some value #{42}"
puts hash[your_string_goes_here]
I don't know if good or bad:
As for this: send "#{destiny}". I assume that your destiny doesn't exist, so you can use method_missing:
def method_missing arg, *args, &block
#do some with "destiny"; save into variable/hash, check if "destiny" is in right place etc.
# return something
end
I have the following two variables:
a = 1;
b = 'a';
I want to be able to do
SOMETYPEOFEVALUATION(b) = 2;
so that the value of variable a is now set to 2.
a # => 2
Is this possible?
Specifically, I am working with the Facebook API. Each object has a variety of different connections (friends, likes, movies, etc). I have a parser class that stores the state of the last call to the Facebook API for all of these connections. These states are all named corresponding to the the GET you have to call in order to update them.
For example, to update the Music connection, you use https://graph.facebook.com/me/music?access_token=... I store the result in a variable called updated_music. For books, its updated_books. If I created a list of all these connection type names, I ideally want to do something like this.
def update_all
connection_list.each do |connection_name|
updated_SomeTypeOfEvalAndConcatenation(connection_name) = CallToAPI("https://graph.facebook.com/me/#{connection_name}?access_token=...")
end
end
Very new to both Rails and StackOverflow so please let me know if there is a better way to follow any conventions.
Tried the below.
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
eval "self.last_#{connection_name}_json = handler.access_api_by_content_type(service, #{connection_name})['data']"
end
#self.last_albums_json = handler.access_api_by_content_type(service, 'albums')['data']
end
end
And I get this error
undefined local variable or method `albums' for #<FacebookParser:0xaa7d12c>
Works fine when I use line that is commented out.
Changing an unrelated variable like that is a bit of a code smell; Most programmers don't like it when a variable magically changes value, at least not without being inside an enclosing class.
In that simple example, it's much more common to say:
a=something(b)
Or if a is a more complex thing, make it a class:
class Foo
attr_accessor :a
def initialize(value)
#a = value
end
def transform(value)
#a = "new value: #{value}"
end
end
baz = "something"
bar = Foo.new(2)
bar.a
=> 2
bar.transform(baz)
bar.a
=> "new value: something"
So while the second example changes an internal variable but not through the accessor, at least it is part of an encapsulated object with a limited API.
Update Ah, I think the question is how do do like php's variable variables. As mu suggests, if you want to do this, you are probably doing the wrong thing... it's a concept that should never have been thought of. Use classes or hashes or something.
how about
eval "#{b}=2"
and with instance variables you can also do instance_variable_set("#name", value)
EDIT:
you can also use send method if you have a setter defined(and you have), try this:
class FacebookParser
attr_accessor :last_albums_json,
def update_parser_vars(service)
handler = FacebookAPIHandler.new
connections_type_list = ['albums']
connections_type_list.each do |connection_name|
send("last_#{connection_name}_json=",
handler.access_api_by_content_type(
service, connection_name)['data']))
end
end
end
problem with your original code is that
eval ".... handler.access_api_by_content_type(service, #{connection_name})"
would execute
... handler.access_api_by_content_type(service, albums)
# instead of
... handler.access_api_by_content_type(service, 'albums')
so you had to write
eval ".... handler.access_api_by_content_type(service, '#{connection_name}')" <- the quotes!
this is why people usually avoid using eval - it's easy to do this kind of mistakes
These sort of things are not usually done using local variables and their names in Ruby. A usual approach could include hashes and symbols:
data = Hash.new
data[:a] = 1 # a = 1
b = :a # b = 'a'
and then, later
data[b] = 2 # SOMETYPEOFEVALUATION(b) = 2
data[:a] # => 2
How would I impelement a function, in ruby, such as the following?
change_me! (val)
update:
What I set out to do was this:
def change_me! (val)
val = val.chop while val.end_with? '#' or val.end_with? '/'
end
This just ended up with....
change_me! 'test#///' => "test#///"
You're thinking about this the wrong way around. While it may be possible to do this in Ruby, it would be overly complicated. The proper way to do this would be:
val.change_me!
Which, of course, varies depending on the class of what you want to change. The point is that, by convention, the methods with '!' affect the class instance on which they're called.
So...
class Changeable
def initialize var
#var = var
end
def change_me! change=1
#var += change
end
end
a = Changeable.new 5 # => New object "Changeable", value 5
a.change_me! 6 # => #var = 7
a.change_me! # => #var = 8
Hope this helps a bit..
You want to do this:
def change_me(val)
val.replace "#{val}!"
end
This replaces the value with a new one. But trust me: You don't usually want to design your ruby code in such a way. Start thinking in objects and classes. Design your code free of side-effects. It will save a whole lot of trouble.
What kind of object is val and how do you want to change it? If you just want to mutate an object (say, an array or a string), what you are asking for works directly:
def change_me!(me)
me << 'abides'
end
val = %w(the dude)
change_me!(val)
puts val.inspect
val = "the dude "
change_me!(val)
puts val.inspect