Set Ruby 2.0 Keyword Arguments with Attr_Accessor on Initialize

Set ruby 2.0 keyword arguments with attr_accessor on initialize

def initialize(keywords: nil, title: nil, url: nil, adsetting: nil)
local_variables.each do |k|
v = eval(k.to_s)
instance_variable_set("@#{k}", v) unless v.nil?
end
end

or following John Ledbetter and Cary Swoveland's suggestion:

def initialize(keywords: nil, title: nil, url: nil, adsetting: nil)
method(__method__).parameters.each do |type, k|
next unless type == :key
v = eval(k.to_s)
instance_variable_set("@#{k}", v) unless v.nil?
end
end

What is attr_accessor in Ruby?

Let's say you have a class Person.

class Person
end

person = Person.new
person.name # => no method error

Obviously we never defined method name. Let's do that.

class Person
def name
@name # simply returning an instance variable @name
end
end

person = Person.new
person.name # => nil
person.name = "Dennis" # => no method error

Aha, we can read the name, but that doesn't mean we can assign the name. Those are two different methods. The former is called reader and latter is called writer. We didn't create the writer yet so let's do that.

class Person
def name
@name
end

def name=(str)
@name = str
end
end

person = Person.new
person.name = 'Dennis'
person.name # => "Dennis"

Awesome. Now we can write and read instance variable @name using reader and writer methods. Except, this is done so frequently, why waste time writing these methods every time? We can do it easier.

class Person
attr_reader :name
attr_writer :name
end

Even this can get repetitive. When you want both reader and writer just use accessor!

class Person
attr_accessor :name
end

person = Person.new
person.name = "Dennis"
person.name # => "Dennis"

Works the same way! And guess what: the instance variable @name in our person object will be set just like when we did it manually, so you can use it in other methods.

class Person
attr_accessor :name

def greeting
"Hello #{@name}"
end
end

person = Person.new
person.name = "Dennis"
person.greeting # => "Hello Dennis"

That's it. In order to understand how attr_reader, attr_writer, and attr_accessor methods actually generate methods for you, read other answers, books, ruby docs.

What is the most efficient way to initialize a Class in Ruby with different parameters and default values?

The typical way to solve this problem is with a hash that has a default value. Ruby has a nice syntax for passing hash values, if the hash is the last parameter to a method.

class Fruit
attr_accessor :color, :type

def initialize(params = {})
@color = params.fetch(:color, 'green')
@type = params.fetch(:type, 'pear')
end

def to_s
"#{color} #{type}"
end
end

puts(Fruit.new) # prints: green pear
puts(Fruit.new(:color => 'red', :type => 'grape')) # prints: red grape
puts(Fruit.new(:type => 'pomegranate')) # prints: green pomegranate

A good overview is here: http://deepfall.blogspot.com/2008/08/named-parameters-in-ruby.html

Is there a way to do recursion with keyword arguments in ruby 2 without re-specifying each argument?

Not sure if a built-in method exists to do so, but it's evidently possible using local_variables and some eval-fu:

def foo(bar: :baz)
Hash[local_variables.map { |k| [k, eval("#{k}")] }]
end

foo # {:bar=>:baz}

Related question:

How do I dynamically create a local variable in Ruby?

Rails optional argument

It's as simple as this:

class Person
attr_accessor :name, :age

def initialize(name = '', age = 0)
self.name = name
self.age = age
end
end

Person.new('Ivan', 20)
Person.new('Ivan')

However, if you want to pass only age, the call would look pretty ugly, because you have to supply blank string for name anyway:

Person.new('', 20)

To avoid this, there's an idiomatic way in Ruby world: options parameter.

class Person
attr_accessor :name, :age

def initialize(options = {})
self.name = options[:name] || ''
self.age = options[:age] || 0
end
end

Person.new(name: 'Ivan', age: 20)
Person.new(age: 20)
Person.new(name: 'Ivan')

You can put some required parameters first, and shove all the optional ones into options.

Edit

It seems that Ruby 2.0 will support real named arguments.

def example(foo: 0, bar: 1, grill: "pork chops")
puts "foo is #{foo}, bar is #{bar}, and grill is #{grill}"
end

# Note that -foo is omitted and -grill precedes -bar
example(grill: "lamb kebab", bar: 3.14)

Setting default values

class Abc
attr_accessor :a, :b, :c

def initialize a = 1, b = 4, c = 0
@a = a
@b = b
@c = c
end

end

This will accept 1, 4, and 0 respectively as default values, but they can be overridden by passing in parameters.

So if you do example = Abc.new without paramaters it will have default values of 1,4,0 but you could do:

     example2 = Abc.new 5, 5 

without passing a value for c and you'd have values of a = 5 and b = 5 with by default c = 0 still.

More broadly, in your Ruby code examples above, you are using brackets where not needed. a def method_name begins a block, and a end will finish it. They serve in place of how brackets are traditionally used in other languages. So for your method getSum you can simply do

def get_sum
#your_code
end

Also, note def getSum (camelCase) would typically be def get_sum (snake_case) in Ruby. Also note in the examples I give above that parenthesis are dropped. They are not needed in Ruby.

Named parameters in Ruby 2

The last example you posted is misleading. I disagree that the behavior is similar to the one before. The last example passes the argument hash in as the first optional parameter, which is a different thing!

If you do not want to have a default value, you can use nil.

If you want to read a good writeup, see "Ruby 2 Keyword Arguments".

Conditional Keyword Arguments (Ruby 2.0)

I feel a code smell here. You are trying to assign the default value to the variables under two separated conditions: when the argument is not given, and when the argument is empty. That is not a good design. It is a potential cause of bugs, and makes maintenance difficult. I suggest you should take one of the two ways:

(i) Make the arguments obilgatory (i.e., pass nil or an empty value rather than not passing an argument), and do the validation in the method body:

class Post
def initialize(title, content)
@title = title.nil? || title.empty? ? "title" : title
@content = content.nil? || content.empty? ? "content" : content
end
end

(ii) Rather than passing an empty value as an argument, do not pass it:

class Post
def initialize(title: "title", content: "content")
@title, @content = title, content
end
end


Related Topics



Leave a reply



Submit