I'm running Ruby 2.3.1 x64 on Windows 10 x64.
My code:
class Credentials
attr_reader :username, :password
def initialize(username = nil, password = nil)
#username = username
#password = password
get_credentials if !#username || !#password #Gets credentials if none are specified
end
def get_credentials
#username = ask("Username: ") { |q| q.echo = true }
#password = ask("Password: ") { |q| q.echo = "*" }
end
end
Ignore the get_credentials wackyness, it's a gem called Highline that I'm using to hide input for security reasons.
When I do the following:
$user = Credentials.new(username: "foo", password: "bar")
I get this return:
#<Credentials:0x000000038ecf30 #password=nil, #username={:username=>"foo", :password=>"bar"}>
Likewise, calling $user.username returns the following:
{:username=>"foo", :password=>"bar"}
when it should be returning:
"foo"
and calling $user.password returns nil.
Can someone tell me why in the name of Henry Hamilton this is happening?! I've used hashed parameters many times, and it always works just fine. Why is it stuffing every parameter setting into a single parameter?
$user = Credentials.new(username: "foo", password: "bar")
You are passing just one parameter to the initialize method, a hash. The hash for the username attribute and nil for the password attribute. Try
$user = Credentials.new("foo", "bar")
Or, if you really want keyword arguments then
def initialize(username: nil, password: nil)
When you define a method/constructor you don't pass arguments by name but by value just like any other programming language, So :
$user=Credentials.new("foo","bar")
Will do what you want.
This is the default in almost every programming language, your question should have been "How did this work", it worked because ruby is dynamically typed and the syntax key1: val1,key2: val2,... is the new hash syntax(since ruby 1.9), a hash is a key-value data structure , so your :
$user=Credentials.new(username: 'foo',password: 'bar')
Is actually calling the constructor with one argument only which is username with the value {username: 'foo',password: 'bar'} and because initialize is defined with default arguments , password got a value of nil.
Now if you do want to pass arguments by name, you have to define the constructor like so :
def initialize(username: nil,password: nil)
//code
end
After that you can do :
$user=Credentials.new(username: 'foo',password: 'bar')
And expect it to behave like you want.
Notice that keyword arguments(that is passing arguments by name) are introduced in ruby 2, also notice that you can achieve the same with a constructor that accepts one parameter which is a hash like this :
def initialize(params={})
//code
end
But this way doesn't limit the number of arguments nor their names(you can call Credentials.new(fooprop: 'foovalue') and no error will be thrown), also it needs some change in code.
The Keyword arguments feature is found in some programming languages and it's useful when the function have many parameters or to make it clear for the programmer what is the parameter for.
def initialize(params={})
#username = params[:username]
#password = params[:password]
#username || #password || get_credentials #simply
end
And then:
$user = Credentials.new(username: "foo", password: "bar")
Related
E.G.
def do_the_thing(file_to_load, hash_path)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data[sections.to_sym]
end
do_the_thing(file_I_want, '[:foo][:bar][0]')
Tried a few methods but failed so far.
Thanks for any help in advance :)
Assuming you missed the parameters names...
Lets assume our file is:
// test.json
{
"foo": {
"bar": ["foobar"]
}
}
Recomended solution
Does your param really need to be a string??
If your code can be more flexible, and pass arguments as they are on ruby, you can use the Hash dig method:
require 'json'
def do_the_thing(file, *hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
data.dig(*hash_path)
end
do_the_thing('test.json', :foo, :bar, 0)
You should get
"foobar"
It should work fine !!
Read the rest of the answer if that doesn't satisfy your question
Alternative solution (using the same argument)
If you REALLY need to use that argument as string, you can;
Treat your params to adapt to the first solution, it won't be a small or fancy code, but it will work:
require 'json'
BRACKET_REGEX = /(\[[^\[]*\])/.freeze
# Treats the literal string to it's correspondent value
def treat_type(param)
# Remove the remaining brackets from the string
# You could do this step directly on the regex if you want to
param = param[1..-2]
case param[0]
# Checks if it is a string
when '\''
param[1..-2]
# Checks if it is a symbol
when ':'
param[1..-1].to_sym
else
begin
Integer(param)
rescue ArgumentError
param
end
end
end
# Converts your param to the accepted pattern of 'dig' method
def string_to_args(param)
# Scan method will break the match results of the regex into an array
param.scan(BRACKET_REGEX).flatten.map { |match| treat_type(match) }
end
def do_the_thing(file, hash_path)
hash_path = string_to_args(hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
data.dig(*hash_path)
end
so:
do_the_thing('test.json', '[:foo][:bar][0]')
returns
"foobar"
This solution though is open to bugs when the "hash_path" is not on an acceptable pattern, and treating it's bugs might make the code even longer
Shortest solution (Not safe)
You can use Kernel eval method which I EXTREMELY discourage to use for security reasons, read the documentation and understand its danger before using it
require 'json'
def do_the_thing(file, hash_path)
file = File.read(file)
data = JSON.parse(file, symbolize_names: true)
eval("data#{hash_path}")
end
do_the_thing('test.json', '[:foo][:bar][0]')
If the procedure you were trying to work with was just extracting the JSON data to an object, you might find yourself using either of the following scenarios:
def do_the_thing(file_to_load)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data[sections.to_sym]
end
do_the_thing(file_I_want)[:foo][:bar][0]
or use the dig function of Hash :
def do_the_thing(file_to_load, sections)
file = File.read(file)
data = JSON.parse(file, { symbolize_names: true })
data.dig(*sections)
end
do_the_thing(file_I_want, [:foo, :bar, 0])
I'm writing an app in Sinatra, using activerecord, so I guess my question is the same as in Rails.
class Entry < ActiveRecord::Base
require 'date'
belongs_to :bankaccount
end
class Recurrent < Entry
attr_accessor :date_1, :date_2, :monthly_entry
def initialize (date_1, date_2)
#date_1 = date_1 # format DateTime.new(2020,12,5)
#date_2 = date_2
end
# other methods
When I run this code, I get :
>> date_1 = DateTime.new(2020,12,5)
>> date_2 = DateTime.new(2021,11,5)
>> recurrent = Recurrent.new(date_1, date_2)
**ArgumentError (wrong number of arguments (given 2, expected 0..1))**
and when I remove the arguments, I get this error message:
>> recurrent = Recurrent.new
**ArgumentError (wrong number of arguments (given 1, expected 2))**
When I do this in plain ruby and run it in irb, so without activerecord, it works fine.
According to the documentation:
Creation
Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when you're receiving the data from somewhere else, like an HTTP request. It works like this:
user = User.new(name: "David", occupation: "Code Artist")
user.name # => "David"
You can also use block initialization:
user = User.new do |u|
u.name = "David"
u.occupation = "Code Artist"
end
And of course you can just create a bare object and specify the attributes after the fact:
user = User.new
user.name = "David"
user.occupation = "Code Artist"
So, ActiveRecord objects allow three different kinds of creating them:
No argument, set attributes later.
Block argument.
One Hash argument.
They don't allow two arguments.
What is the best way to write a function (or something DSLish) that will allow me to write this code in Ruby. How would I construct the function write_pair?
username = "tyndall"
write_pair username
# where write_pair username outputs
username: tyndall
Is it possible to do? Looking for the most simple way to do this.
Sure it is possible!
My solution tests the var by Object#object_id identity: http://codepad.org/V7TXRxmL
It's crippled in the binding passing style ...
Although it works just for local vars yet, it can be easily be made "universal" adding use of the other scope-variable-listing methods like instance_variables etc.
# the function must be defined in such a place
# ... so as to "catch" the binding of the vars ... cheesy
# otherwise we're kinda stuck with the extra param on the caller
#_binding = binding
def write_pair(p, b = #_binding)
eval("
local_variables.each do |v|
if eval(v.to_s + \".object_id\") == " + p.object_id.to_s + "
puts v.to_s + ': ' + \"" + p.to_s + "\"
end
end
" , b)
end
# if the binding is an issue just do here:
# write_pair = lambda { |p| write_pair(p, binding) }
# just some test vars to make sure it works
username1 = "tyndall"
username = "tyndall"
username3 = "tyndall"
# the result:
write_pair(username)
# username: tyndall
If it's possible for you to use a symbol instead of the variable name, you could do something like this:
def wp (s, &b)
puts "#{s} = #{eval(s.to_s, b.binding)}"
end
In use:
irb(main):001:0> def wp (s, &b)
irb(main):002:1> puts "#{s} = #{eval(s.to_s, b.binding)}"
irb(main):003:1> end
=> nil
irb(main):004:0> var = 3
=> 3
irb(main):005:0> wp(:var) {}
var = 3
Note that you must pass the empty block {} to the method or it cannot get the binding to evaluate the symbol.
You can't actually get a variable's name in Ruby. But you could do something like this:
data = {"username" => "tyndall"}
Or even,
username = "tyndall"
data = {"username", "password", "favorite_color"}
data.each { |param|
value = eval(param)
puts "#{param}: #{value}"
}
I made a vim macro for this:
" Inspect the variable on the current line (in Ruby)
autocmd FileType ruby nmap ,i ^"oy$Iputs "<esc>A: #{(<esc>"opA).inspect}"<esc>
Put the variable you'd like to inspect on a line by itself, then type ,i (comma then i) in normal mode. It turns this:
foo
into this:
puts "foo: #{(foo).inspect}"
This is nice because it doesn't have any external dependencies (e.g. you don't have to have a library loaded up to use it).
Building on previous answers relating to symbols & bindings ... if passing in the variable name as a symbol works for you (who doesn't love cutting out extra keystrokes?!), try this:
def wp(var_name_as_sym)
# gets caller binding, which contains caller's execution environment
parent_binding = RubyVM::DebugInspector.open{|i| i.frame_binding(2) }
# now puts the symbol as string + the symbol executed as a variable in the caller's binding
puts %Q~#{var_name_as_sym.to_s} = #{eval("#{var_name_as_sym.to_s}.inspect", parent_binding)}~
end
aa=1
bb='some bb string'
os = OpenStruct.new(z:26, y:25)
Console output:
> wp :aa
aa = 1
=> nil
> wp :bb
bb = "some bb string"
=> nil
> wp :os
os = #<OpenStruct z=26, y=25>
=> nil
Using ruby 2.2.2p95
(Credit to banister for getting binding of calling context)
This is a simple solution:
def write_pair(variable)
puts variable + eval(variable)
end
This is more readable:
def write_pair(variable)
puts 'A' * 100
puts variable + ': ' + eval(variable).inspect
puts 'Z' * 100
end
Invocation:
write_pair "variable"
def write_pair var, binding
puts "#{ var } = #{ eval(var, binding)}"
end
username = "tyndall"
write_pair "username", binding
This seems weird because binding is never defined, but it works. From Ruby: getting variable name:
The binding() method gives a Binding object which remembers the
context at the point the method was called. You then pass a binding
into eval(), and it evaluates the variable in that context.
Be sure to pass a string, not the variable.
# make use of dynamic scoping via methods and instance vars
#_binding = binding
def eval_debug(expr, binding = #_binding)
"#{expr} => #{eval(expr, binding)}"
end
# sample invocation:
x = 10
puts eval_debug "x"
puts eval_debug "x**x"
I am new to Ruby and Rspec, and so I happened to found this bit of code:
Here is my Specification:
RSpec.describe Surveyor::Answer, '03: Answer validations' do
context "for a free text question" do
let(:question) { double(Surveyor::Question, type: 'free_text') }
# NOTE: The rating validations should not apply for 'free_text' questions.
subject { described_class.new(question: question, value: 'anything') }
it { should be_valid }
end
Here is my Class:
module Surveyor
class Answer
def initialize(question_answer)
#question = question_answer[:question]
#answer = question_answer[:value]
end
def question_type
# I want to check what is the type of question here.
# 'free_text' or 'rating'
# if free_text
# print question type
# else
# do something
end
end
My question is how can I print(puts) the type of question (free_text/rating) in Answer class?
When I tried using print question_answer[:question]it only gave me #<Double Surveyor::Question>
So I could not use question_answer[:question][:type]
You can access the type in the constructor simply: question_answer[:question].type, or later in object level methods: #question.type.
You can't access it like question_answer[:question][:type] because the double method in the test creates a classic like object rather than a hash.
A tip: when a method accepts parameters as a single hash you can simply name that as options or params but if you have only 3-4 params, you can use separate variables for params instead of a hash
OK, I am trying to write a simple object that will contain two strings, one a "user password" and one a "target password," this would be needed if you wanted to script a password change on a remote server using sudo (the first password would be to perform the sudo command, the "target password" would be the string to which the password should be reset.
I want the user to be prompted once for the first password, and then the user will have five tries to enter a second password string and repeat it accurately. What I came up with, the code below, does not seem to work. Any ideas?
require 'pp'
require 'RubyGems'
require 'highline/import' #gem install highline
class Authorization
attr_reader :user_password , :target_password
pass_code = lambda {
first_attempt = ask("Enter target password: "){ |q| q.echo = '*' }
second_attempt = ask("Re-enter password to verify"){ |q| q.echo = '*'}
}
### So we need some sort of recursive loop
def initialize(target_pass=false)
#user_password = ask("Enter your admin password: ") { |q| q.echo = '*' }
if target_pass
count = 1
while n < 6
pass_code
if first_attempt == second_attempt
#target_password = first_attempt
return
else
count += 1
end
end
end
end
end
my_pass = Authorization.new(true)
pp "pass" , my_pass
I see several problems
It's require "rubygems" (not RubyGems)
Also, if using Ruby 1.9, loading rubygems isn't necessary.
The lambda has locally scoped variables assigned that aren't available in the constructor
The lambda definition itself is out of scope for access anyway
The loop never terminates (btw, this isn't recursion).
Try something like this instead.
require "highline/import"
class Authorization
attr_accessor :user_password, :target_password
def prompt(prompt_for_target = false)
self.user_password = ask_for_password("Enter your admin password")
return unless prompt_for_target
5.times do
password = ask_for_password("Enter target password")
confirmation = ask_for_password("Re-enter password to verify")
if password == confirmation
self.target_password = password
return
end
end
end
private
def ask_for_password(message)
ask("#{message}: ") { |q| q.echo = '*' }
end
end
auth = Authorization.new
auth.prompt(true)
puts auth.user_password
puts auth.target_password
Pretty straightforward and similar to Ryan's solution:
require 'highline/import' #gem install highline
class Authorization
attr_reader :admin_password, :target_password
def initialize
#admin_password = ask_for_password("Enter your admin password: ")
5.times do
#target_password = ask_for_password("Enter target password: ")
verify_target_pass = ask_for_password("Re-enter password to verify: ")
break if #target_password == verify_target_pass
#target_password = nil
end
end
private
def ask_for_password(message)
ask(message) {|q| q.echo = "*"}
end
end
my_pass = Authorization.new
puts "Administrator's password is: #{my_pass.admin_password}"
puts "Target password is: #{my_pass.target_password}"
First, thank you all for your answers. This was my first question, and, unfortunately, came out a little garbled, but all of you seemed to understand it really well.
IMHO, what I was trying to do recursion, but I am not sure that this is the best place for that discussion.
I am using Ruby 1.8.7, which I probably should have mentioned at the beginning of the post. Ryan's solution worked, but only when I took out the references to "self" and substituted in the instance variable:
#user_password = ask_for_password("Enter your admin password") #instead of
self.user_password = ask_for_password("Enter your admin password")
this might not be necessary for Ruby 1.9, but it does have the advantage of making prostosuper's almost identical to Ryan's.
Once again, thank you all! This has been a great "getting my feet wet" experience.
First, this isn't recursion. This is iteration. A recursive function would call itself again:
def factorial(n)
if (n == 0)
1
else
n * factorial(n-1)
end
end
Now, for the specific problem you've got; you are trying to loop with the condition:
while n < 6
But note that in the body of your loop, you're doing nothing to change the value of n. So your loop cannot terminate. (Further, since you've forgotten to assign a value to n in the first place, it probably cannot start, either. :)