I have following code
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
puts u.name #=> john
In the above case everything works. However this code does not work
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
u.name('john')
In order to fix that issue, I have decided to use alias_method. I know there are other ways to solve the problem, but I'm specifically looking if this problem could be solved using alias_method. Trying to learn.
Following code will work
class User
attr_accessor :name
alias_method :foo, :name=
end
u = User.new
u.foo('john')
puts u.name
However this code will not work for obvious reason.
class User
attr_accessor :name
alias_method :name, :name=
end
u = User.new
u.name('john')
puts u.name
Anyone knows if this problem can be fixed with alias_method.
I don't really get what you're trying to do. By writing attr_acccessor :name, the name= method gets created automatically. If you're trying to make this work:
user.name("john")
you can add this method:
class User
attr_accessor :name
def name(n)
self.name = n
end
end
The short version is alias method cannot help you here.
It looks like you're trying to merge the getter and setter created by attr_accessor. The reason this isn't working is because you're overriding the getter with the setter when you call alias.
To get the functionality you're looking for you'll have to define your own method.
Something like this:
class User
attr_accessor :name
def name(set_value = "VALUE_THAT_WILL_NEVER_BE_USED_AS_A_NAME")
unless set_value == "VALUE_THAT_WILL_NEVER_BE_USED_AS_A_NAME"
#name = set_value
end
#name
end
end
u = User.new
u.name('john') #=> sets name to john
puts u.name #=> puts john
# bonus! doesn't override name=
u.name = 'mike' # => sets name to mike
puts u.name #=> puts mike
Edit: Corrected method to allow setting of nil or false.
If you want to use a single getter/setter method beware of using "nil" since you may want to set your attribute to nil.
u.name("john")
u.name(nil) # name still will be john
So do something which checks that there are args
class U
attr_reader :n
def n(*args)
if(args.length > 0)
#n = args.first
else
#n
end
end
end
u = U.new
u.n "j"
u.n # j
u.n nil
u.n # nil
class User
attr_accessor :name
end
u = User.new
u.name = 'john'
u.name('john')
In this code u.name would be the getter for name. It looks like you're trying to call it as a setter? Is that want you want to do? If that's the case then you wouldn't use attr_accessor, you'd need to write the getters and setters yourself.
Related
Okay, this is a little hard to explain but I will try (For starters I am only just learning to code so it may be something super simple I'm missing..)
I created a few classes, I put a name in those classes, I put them in an array, I then chose one at random and try to puts the name, and it outputs blank.
Am I doing this all completely wrong? I've been learning ruby for about 3 months now so I'm sure there is a lot I don't know.
class A
attr :name
def set_name
#name = "Aaa"
end
def get_name
return #name
end
end
class B
attr :name
def set_name
#name = "Bbb"
end
def get_name
return #name
end
end
class C
attr :name
def set_name
#name = "Ccc"
end
def get_name
return #name
end
end
name_a = A.new
name_b = B.new
name_c = C.new
which_name = Array.new
which_name[0] = name_a
which_name[1] = name_b
which_name[2] = name_c
roll = rand(max 3)
puts which_name[roll].get_name
I then chose one at random and try to puts the name, and it outputs
blank.
You never called the #set_name method in your code. You can add this:
name_a.set_name
name_b.set_name
name_c.set_name
Also, you probably want to look into #attr_accessor.
Consider the following class:
class Person
attr_accessor :first_name
def initialize(&block)
instance_eval(&block) if block_given?
end
end
When I create an instance of Person as follows:
person = Person.new do
first_name = "Adam"
end
I expected the following:
puts person.first_name
to output "Adam". Instead, it outputs only a blank line: the first_name attribute has ended up with a value of nil.
When I create a person likes this, though:
person = Person.new do
#first_name = "Adam"
end
The first_name attribute is set to the expected value.
The problem is that I want to use the attr_accessor in the initialization block, and not the attributes directly. Can this be done?
Ruby setters cannot be called without an explicit receiver since local variables take a precedence over method calls.
You don’t need to experiment with such an overcomplicated example, the below won’t work as well:
class Person
attr_accessor :name
def set_name(new_name)
name = new_name
end
end
only this will:
class Person
attr_accessor :name
def set_name(new_name)
# name = new_name does not call `#name=`
self.name = new_name
end
end
For your example, you must explicitly call the method on a receiver:
person = Person.new do
self.first_name = "Adam"
end
If the code is run with warnings enabled (that is ruby -w yourprogram.rb)
it responds with : "warning: assigned but unused variable - first_name", with a line-number pointing to first_name = "Adam". So Ruby interprets first_name as a variable, not as a method. As others have said, use an explicit reciever: self.first_name.
Try this:
person = Person.new do |obj|
obj.first_name = "Adam"
end
puts person.first_name
I want to use the attr_accessor in the initialization block, and not the attributes directly
instance_eval undermines encapsulation. It gives the block access to instance variables and private methods.
Consider passing the person instance into the block instead:
class Person
attr_accessor :first_name
def initialize
yield(self) if block_given?
end
end
Usage:
adam = Person.new do |p|
p.first_name = 'Adam'
end
#=> #<Person:0x00007fb46d093bb0 #first_name="Adam">
How to implement inheritance in ruby for the following?
class Land
attr_accessor :name, :area
def initialize(name, area)
#name = name
#area = area
end
end
class Forest < Land
attr_accessor :rain_level
attr_reader :name
def name=(_name)
begin
raise "could not set name"
rescue Exception => e
puts e.message
end
end
def initialize(land, rain_level)
#name = land.name
#rain_level = rain_level
end
end
l = Land.new("land", 2300)
f = Forest.new(l, 400)
puts f.name # => "land"
suppose when i change name for land l, then it should change for sub class also
l.name ="new land"
puts f.name # => "land"
what expected is puts f.name # => "new land"
It seems to me that this is not actually inheritance in the OO sense. If you change Forest so that it holds a reference to the Land then you will get the behavior you wanted.
class Forest
attr_accessor :rain_level
def name
#land.name
end
def initialize(land, rain_level)
#land = land
#rain_level = rain_level
end
end
This is kind of an interesting thing you want to build.
Summarizing you want to have two objects that share a value but only one is allowed to edit the value, the other one is only allowed to read it.
I think the easiest way to implement this is in your case to implement a new getter in Forest which returns land.name. By writing l.name = 'meow' will f.name return moew too because it holds a reference to l.
Hope this helps.
I'm studying Ruby and my brain just froze.
In the following code, how would I write the class writer method for 'self.total_people'? I'm trying to 'count' the number of instances of the class 'Person'.
class Person
attr_accessor :name, :age
##nationalities = ['French', 'American', 'Colombian', 'Japanese', 'Russian', 'Peruvian']
##current_people = []
##total_people = 0
def self.nationalities #reader
##nationalities
end
def self.nationalities=(array=[]) #writer
##nationalities = array
end
def self.current_people #reader
##current_people
end
def self.total_people #reader
##total_people
end
def self.total_people #writer
#-----?????
end
def self.create_with_attributes(name, age)
person = self.new(name)
person.age = age
person.name = name
return person
end
def initialize(name="Bob", age=0)
#name = name
#age = age
puts "A new person has been instantiated."
##total_people =+ 1
##current_people << self
end
You can define one by appending the equals sign to the end of the method name:
def self.total_people=(v)
##total_people = v
end
You're putting all instances in ##current_people you could define total_people more accurately:
def self.total_people
##current_people.length
end
And get rid of all the ##total_people related code.
I think this solves your problem:
class Person
class << self
attr_accessor :foobar
end
self.foobar = 'hello'
end
p Person.foobar # hello
Person.foobar = 1
p Person.foobar # 1
Be aware of the gotchas with Ruby's class variables with inheritance - Child classes cannot override the parent's value of the class var. A class instance variable may really be what you want here, and this solution goes in that direction.
One approach that didn't work was the following:
module PersonClassAttributes
attr_writer :nationalities
end
class Person
extend PersonClassAttributes
end
I suspect it's because attr_writer doesn't work with modules for some reason.
I'd like to know if there's some metaprogramming way to approach this. However, have you considered creating an object that contains a list of people?
Given the following class:
class Test
attr_accessor :name
end
When I create the object, I want to do the following:
t = Test.new {name = 'Some Test Object'}
At the moment, it results in the name attribute still being nil.
Is that possible without adding an initializer?
ok,
I came up with a solution. It uses the initialize method but on the other hand do exactly what you want.
class Test
attr_accessor :name
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
end
end
def display
puts #name
end
end
t = Test.new :name => 'hello'
t.display
happy ? :)
Alternative solution using inheritance. Note, with this solution, you don't need to explicitly declare the attr_accessor!
class CSharpStyle
def initialize(init)
init.each_pair do |key, val|
instance_variable_set('#' + key.to_s, val)
instance_eval "class << self; attr_accessor :#{key.to_s}; end"
end
end
end
class Test < CSharpStyle
def initialize(arg1, arg2, *init)
super(init.last)
end
end
t = Test.new 'a val 1', 'a val 2', {:left => 'gauche', :right => 'droite'}
puts "#{t.left} <=> #{t.right}"
As mentioned by others, the easiest way to do this would be to define an initialize method. If you don't want to do that, you could make your class inherit from Struct.
class Test < Struct.new(:name)
end
So now:
>> t = Test.new("Some Test Object")
=> #<struct Test name="Some Test Object">
>> t.name
=> "Some Test Object"
There is a general way of doing complex object initialization by
passing a block with necessary actions. This block is evaluated in the
context of the object to be initialized, so you have an easy access to
all instance variables and methods.
Continuing your example, we can define this generic initializer:
class Test
attr_accessor :name
def initialize(&block)
instance_eval(&block)
end
end
and then pass it the appropriate code block:
t = Test.new { #name = 'name' }
or
t = Test.new do
self.name = 'name'
# Any other initialization code, if needed.
end
Note that this approach does not require adding much complexity
to the initialize method, per se.
As previously mentioned, the sensible way to do this is either with a Struct or by defining an Test#initialize method. This is exactly what structs and constructors are for. Using an options hash corresponding to attributes is the closest equivalent of your C# example, and it's a normal-looking Ruby convention:
t = Test.new({:name => "something"})
t = Test.new(name: "something") # json-style or kwargs
But in your example you are doing something that looks more like variable assignment using = so let's try using a block instead of a hash. (You're also using Name which would be a constant in Ruby, we'll change that.)
t = Test.new { #name = "something" }
Cool, now let's make that actually work:
class BlockInit
def self.new(&block)
super.tap { |obj| obj.instance_eval &block }
end
end
class Test < BlockInit
attr_accessor :name
end
t = Test.new { #name = "something" }
# => #<Test:0x007f90d38bacc0 #name="something">
t.name
# => "something"
We've created a class with a constructor that accepts a block argument, which is executed within the newly-instantiated object.
Because you said you wanted to avoid using initialize, I'm instead overriding new and calling super to get the default behavior from Object#new. Normally we would define initialize instead, this approach isn't recommended except in meeting the specific request in your question.
When we pass a block into a subclass of BlockInit we can do more than just set variable... we're essentially just injecting code into the initialize method (which we're avoiding writing). If you also wanted an initialize method that does other stuff (as you mentioned in comments) you could add it to Test and not even have to call super (since our changes aren't in BlockInit#initialize, rather BlockInit.new)
Hope that's a creative solution to a very specific and intriguing request.
The code you're indicating is passing parameters into the initialize function. You will most definitely have to either use initialize, or use a more boring syntax:
test = Test.new
test.name = 'Some test object'
Would need to subclass Test (here shown with own method and initializer) e.g.:
class Test
attr_accessor :name, :some_var
def initialize some_var
#some_var = some_var
end
def some_function
"#{some_var} calculation by #{name}"
end
end
class SubClassedTest < Test
def initialize some_var, attrbs
attrbs.each_pair do |k,v|
instance_variable_set('#' + k.to_s, v)
end
super(some_var)
end
end
tester = SubClassedTest.new "some", name: "james"
puts tester.some_function
outputs: some calculation by james
You could do this.
class Test
def not_called_initialize(but_act_like_one)
but_act_like_one.each_pair do |variable,value|
instance_variable_set('#' + variable.to_s, value)
class << self
self
end.class_eval do
attr_accessor variable
end
end
end
end
(t = Test.new).not_called_initialize :name => "Ashish", :age => 33
puts t.name #=> Ashish
puts t.age #=> 33
One advantage is that you don't even have to define your instance variables upfront using attr_accessor. You could pass all the instance variables you need through not_called_initialize method and let it create them besides defining the getters and setters.
If you don't want to override initialize then you'll have to move up the chain and override new. Here's an example:
class Foo
attr_accessor :bar, :baz
def self.new(*args, &block)
allocate.tap do |instance|
if args.last.is_a?(Hash)
args.last.each_pair do |k,v|
instance.send "#{k}=", v
end
else
instance.send :initialize, *args
end
end
end
def initialize(*args)
puts "initialize called with #{args}"
end
end
If the last thing you pass in is a Hash it will bypass initialize and call the setters immediately. If you pass anything else in it will call initialize with those arguments.