In one of my helpers I have to differ if the user is on a profile or not. The helper returns an array full of URLs.
def get_options(profile)
if profile
url_arr = profile_infinite_path(user, ...)
# .
# .
# do stuff with profile_infinite_path
else
url_arr = infinite_path(user, ...)
# .
# .
# do same stuff with infinite_path
end
end
I want to make this code more DRY, so my plan was to store the path as a variable and then simply calling all the remaining code only once.
def get_options(profile)
if profile
var_path = profile_infinite_path
else
var_path = infinite_path
end
url_arr = var_path(user, ...)
# .
# .
# do stuff with var_path
end
I also tried storing the path as a method, but no luck there.
var_path = profile_infinite_path.method
You have two options here. Since path helpers are methods, and methods can be invoked without arguments simply by stating them, an assignment like path = profile_inifite_path gives the result of the method invocation.
You can defer invocation by using a symbol to refer to the method, and then sending it as a message when needed:
var_path = :profile_infinite_path
# ...
send(var_path, user, ...)
The symbol is the first argument to send, and it is followed by any arguments you would have given to the method.
The other way you could handle this is to wrap the method call in a proc and invoke it when needed:
var_path = ->(*args){ profile_infinite_path(*args) }
# ...
var_path.call(user, ...)
I tend to prefer send for situations like this.
Store only a symbol in your variable eg
var_path = :profile_infinite_path
Then you can do send(var_path, other_args) to get the real URL.
Eg if you have users:
var_path = :user_path
send(var_path, 2) would return "/users/2"
In ruby you can assign the results of an if-else expression to a variable. This allows you to call the desired method and assign the results like so:
url_arr = if profile
profile_infinite_path(user, ...)
else
infinite_path(user, ...)
end
# .
# .
# do stuff with url_arr
Related
I am comparing data type values from expected value which stored in database to actual response.I am using below code for validating value .I am getting undefined "string_between_markers" method error while comparing.I understood there is no predefined method is available in expected_value string.How to call string_between_markers for compare regex.
In DB Pattern regex has below value Pattern_Regex_[^([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3}).([0-9]{1,3})]
def validate_value? (actual_value)
if expected_value.include? "Pattern_Regex"
# get the regex from []
regex = expected_value.string_between_markers("[","]")
if expected_value.match(regex)
result = "Passed"
end
end
#String Between Method
def string_between_markers marker1, marker2
self[/#{Regexp.escape(marker1)}(.*?)#{Regexp.escape(marker2)}/m, 1]
end
end
The issue is that you are calling a method on String that is not defined correctly. There are two ways.
First solution
def string_between_markers(input, marker1, marker2)
input[/#{Regexp.escape(marker1)}(.*)#{Regexp.escape(marker2)}/m, 1]
end
and then call it like this:
string_between_markers(expected_value, "[","]")
Second solution
Create a file like lib/ext/string.rb with the following code:
class String
def string_between_markers(marker1, marker2)
self[/#{Regexp.escape(marker1)}(.*)#{Regexp.escape(marker2)}/m, 1]
end
end
Then, in your code, you have to require this file by calling require 'ext/string' or by adding lib folder to autoload paths.
After that, you are calling the method as you did before:
expected_value.string_between_markers("[","]")
I have a Model user with the following method:
def number_with_hyphen
number&.insert(8, "-")
end
When I run it several times in my tests I get the following output:
users(:default).number_with_hyphen
"340909-1234"
(byebug) users(:default).number_with_hyphen
"340909--1234"
(byebug) users(:default).number_with_hyphen
"340909---1234"
(byebug) users(:default).number_with_hyphen
"340909----1234"
It changes the number ?Here are the docs https://apidock.com/ruby/v1_9_3_392/String/insert
When I restructure my method to:
def number_with_hyphen
"#{number}".insert(8, "-") if number
end
If works like expected. The output stays the same!
How would you structure the code, how would you perform the insert?
which method should I use instead. Thanks
If you're using the insert method, which in the documentation explicitly states "modifies str", then you will need to avoid doing this twice, rendering it idempotent, or use another method that doesn't mangle data.
One way is a simple regular expression to extract the components you're interested in, ignoring any dash already present:
def number_with_hyphen
if (m = number.match(/\A(\d{8})\-?(\d+)\z/))
[ m[1], m[2] ].join('-')
else
number
end
end
That ends up being really safe. If modified to accept an argument, you can test this:
number = '123456781234'
number_with_hyphen(number)
# => "12345678-1234"
number
# => "123456781234"
number_with_hyphen(number_with_hyphen(number))
# => "12345678-1234"
number_with_hyphen('1234')
# => "1234"
Calling it twice doesn't mangle anything, and any non-conforming data is sent through as-is.
Do a clone of the string:
"#{number}".clone.insert(8, '-')
I am trying to run a program from a book that I am learning ruby from, but I keep getting this error no matter how I try to fix it
ex41.rb:70:in `convert': wrong number of arguments (0 for 2) (ArgumentError)
from ex41.rb:117:in `block (2 levels) in <main>'
from ex41.rb:113:in `each'
from ex41.rb:113:in `block in <main>'
from ex41.rb:108:in `loop'
from ex41.rb:108:in `<main>'
I do understand that I have the wrong number of arguments, but the thing is I can't find where I'm getting them wrong, could someone please explain this to me? For convienience I put (LINE XXX(as a number)) by the lines that cause me trouble. Thank you.
# calls external file from web
require 'open-uri'
# assigns WORD_URL to website
WORD_URL = "http://learncodethehardway.org/words.txt"
# creates empty array WORDS
WORDS =[]
# creates hash PHRASES
PHRASES = {
"class ### < ### \nend" =>
"Make a class named ### that is-a ###.",
# my assumption is that ### ### and the like will sb in words, lets find
out
"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 equal to '***'."
}
# creates variable for first argument if argument is english
PHRASE_FIRST = ARGV[0] == "english"
# opens WORD_URL function and creates function f
open(WORD_URL) {|f|
# for every line in f chomps break character and pushes out word to f
f.each_line {|word| WORDS.push(word.chomp)}
}
# defines function craft_names with variables rand_words, snippet and
pattern, and assigns caps value to false
def craft_names(rand_words, snippet, pattern, caps=false)
# assigns vriable names to snippet.scan on pattern maps and does the
following
function
names = snippet.scan(pattern).map do
# assigns word to rand_words that have popped off the end
word = rand_words.pop()
# Guessing again, maybe capitalizes the first word
caps ? word.capitalize : word
# ends do
end
# returns names twice
return names * 2
# ends craft_names function
end
# defines function craft_params with variables rand_words snippet, and
pattern
def craft_params(rand_words, snippet, pattern)
# assigns names to action scan with variable pattern with action
# length on beginning part of array to snippet and runs the following
# function using map
names = (0...snippet.scan(pattern).length).map do
# assigns variable param_count to an action that takes a random 3 and adds one to it
param_count = rand(3) + 1
# assigns variable params to one long ass action that maps 0 to param_count
value and pops out the last word
params = (0...param_count).map { |x| rand_words.pop() }
# joins params list, (I'm sure its a list) with string ,
params.join(", ")
# ends names for loop
end
# returns names twice
return names * 2
# ends craft_params function
end
# defines convert function with variables snippet and phrase
(LINE 70) def convert(snippet, phrase)
# sords words randomly and assigns words to rand_words
rand_words = WORDS.sort_by {rand}
# assigns class names to function craft mnames on rand_words, snippet /###/, and caps=true
class_names = craft_names(rand_words, snippet, /###/, caps=true)
# assigns other_names to craft_names with variables rand_words, snippet, and /\*\*\*/
other_names = craft_names(rand_words, snippet, /\*\*\*/)
# assigns param_names to craft_params with rand_words, snippet, and ### as variables
param_names = craft_params(rand_words, snippet, /###/)
# assigns empty array results
results = []
# on all variables snippet and phrase matchups perform the function
sentence
[snippet, phrase].each do |sentence|
# fake class names, also copies sentence
result = sentence.gsub(/###/) {|x| param_names.pop}
# fake other names
result.gsub!(/\*\*\*/)
# fake parameter lists
result.gsub!(/###/)
# returns result
results.push(result)
# ends function
end
# returns results
return results
# ends convert function
end
# keep going until they hit ctrl-d
# continual loop
(LINE 108)loop do
# assigns variable snippets to Prases.keys and sorts randomly
snippets = PHRASES.keys().sort_by {rand}
# for snippet in snippets
(LINE113) snippets.each do |snippet|
# assigns PHRASES on snippet to phrase
phrase = PHRASES[snippet]
# asssigns question and answer to converted snippet and phrase
#respectively
(LINE117)question, answer = convert[snippet, phrase]
# if values in phrase firs are equal to answer, question
if PHRASE_FIRST
# question, answer = answer, question
question, answer = answer, question
# ends if
end
# prints question, two line breaks, and > without line break
print question, "\n\n> "
# closes program unless input
exit(0) unless $stdin.gets
# prints line break ANSWER: answer line break line break
puts "\nANSWER: %s\n\n"
# ends unless
end
# ends loop
end
TL;DR – Use convert(snippet, phrase) instead of convert[snippet, phrase]
When you write
convert[snippet, phrase]
... it is equivalent to:
convert()[snippet, phrase]
Adding a space:
convert [snippet, phrase]
... would call the method with an array:
convert([snippet, phrase])
To actually call the method with two arguments, use:
convert(snippet, phrase)
or
convert snippet, phrase
I am using Ruby on Rails 3.0.9 and RSpec 2. I am trying to refactoring some spec file in the following way (in order to test with less code similar class object attribute values):
[
:attribute_a,
:attribute_b,
:attribute_c
].each do |attr|
before do
# HERE I would like to set the "current" 'attr' related to the
# class object instance 'attribute_< letter >' (read below for
# more information) each time the iterator is called (note: all
# following attributes are NOT attr_accesible - for that reason
# I use the 'user.attribute_< letter >' in the 'before do'
# statement)
#
# # Iteration 1
# user.attribute_a = 'a_value'
# # No changes to 'user.attribute_b'
# # No changes to 'user.attribute_c'
#
# # Iteration 2
# # No changes to 'user.attribute_a'
# user.attribute_b = 'a_value'
# # No changes to 'user.attribute_c'
#
# # Iteration 3
# # No changes to 'user.attribute_a'
# # No changes to 'user.attribute_b'
# user.attribute_c = 'a_value'
# Maybe I should make something like the following but that, as well
# as the 'send' method must be used, doesn't work (the below code-line
# is just an example of what I would like to do).
#
# user.send(:"#{attr}") = 'a_value'
end
...
end
How can I improve the above code so to accomplish what I aim (I refer to the user.send(:"#{attr}") = 'a_value' part in order to set "programmatically" - that is, set a different attribute value for each iteration made - each user attribute value to 'a_value')?
You should use .send and append an = to the method name to invoke the setter, passing the value as the second argument to send:
[
:attribute_a,
:attribute_b,
:attribute_c
].each do |attr|
before do
user.send("#{attr}=", 'a_value')
end
You're effectively doing this:
user.send('attribute_a=', 'a_value');
Your syntax (user.send(:"#{attr}") = 'a_value') is wrong/weird for a couple reasons:
There's no reason to convert :attr to a string and them immediately back to a symbol
You can't assign a value to the return value of .send
I have this code that's working:
case index
when "Books"
#reading_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#reading_link = create_amz_url(search, asin)
tempy = #nowreading.content.match(/#nowreading.*/).to_s.gsub("#nowreading",'') # strips away the #nowreading tag
#nowreading.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #reading_link)
# replaces the book title (passed in as variable 'search') with the URL'ed title
when "Music"
#listening_img = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
#listening_link = create_amz_url(search, asin)
tempy = #nowlistening.content.match(/#nowlistening.*/).to_s.gsub("#nowlistening",'') # strips away the #nowreading tag
#nowlistening.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", #listening_link)
# replaces the song title (passed in as variable 'search') with the URL'ed title
end
I need to repeat this for many, many categories. I tried something like this to DRY the code but it didn't work:
def get_img_and_links(act, res, search, asin)
'#'+act+'ing_img' = res.items.first.get_hash('MediumImage')["URL"] # don't show an image
'#'+act+'ing_link' = create_amz_url(search, asin)
tempy = '#now'+act+'ing'.content.match(/#now"#{act}"ing.*/).to_s.gsub("#now#{act}ing",'') # strips away the #nowreading tag
'#now'+act+'ing'.content = tempy.match(/#{search}.*/).to_s.gsub("#{search}", '#'+act+'ing_link')
# replaces the book title (passed in as variable 'search') with the URL'ed title
end
Essentially, I was trying to create a function that took an "act" (e.g., "read", "listen", etc) and have the variables within that function be dynamic. Can this be accomplished? If so, how?
Look up instance_variable_set here: http://ruby-doc.org/core/classes/Object.html. It's what you need to dynamically create these variables.
instance_variable_set "##{act}ing_img".to_sym, res.items.first.get_hash('MediumImage')["URL"]
And so on...
Good looking out trying to dry up your code. I would definitely use some hashes there instead of instance variables. Then you can use the key as the action. Just a thought.
IMPO, I think you should use more generic variables. Although the creating variables are supported by ruby, it will make your code hard to read
my suggestion is having some generic names like
#img (instead of reading_img, listing_img etc..)
#link (instead of reading_link, listing_link etc..)
and something like #content, because as your login at a given time only one will be selected and this wouldn't be a problem
Again, this is what i understood from the code you posted and correct me if I'm wrong
cheers
sameera
you should do something like this:
def setup
#reading_img = get_img(whatever)
#reading_link = get_link(whatever)
#listening_img = get_img(whatever)
#listening_link = get_link(whatever)
end
def get_img(whatever)
...do stuff and return stuff...
end
def get_link(whatever)
...do stuff and return stuff...
end