# Attribute writers

**URL:** <https://www.ruby-forum.com/t/attribute-writers/51727>\
**Category:** Ruby\
**Created:** [January 6, 2006, 10:16am UTC](https://www.ruby-forum.com/t/attribute-writers/51727 "2006-01-06T10:16:36Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 6, 2006, 10:16am UTC](https://www.ruby-forum.com/t/attribute-writers/51727/1 "2006-01-06T10:16:36Z")

</div>

I’m working on a survey system in Ruby and am having a style crisis with  
a simple bit of code that has left me paralyzed and unable to work.

Respondent data in research surveys is traditionally stored as big  
strings of text like this: “123145992348241110111 11 11 1 1111 3331 1”

This was a made up example, in the real world however, there’s sometimes  
hundreds of thousands of characters in each respondent’s data string.  
In the stats and analysis section of my program, I’m working out how to  
access subsets of these strings in an object-oriented way.

Something like this appeals to me and is very readable:

if currentRespondent.ascii(0…2) == 123  
currentRespondent.ascii(0…2) = 777  
end

How would one actually implement this?

class Respondent  
def ascii(range)  
return @ascii[range]  
end

# doesn’t work

def ascii(range)=(value)  
@ascii[range] = value  
end  
end

currentRespondent = Respondent.new

This is broken code, as Ruby doesn’t like me trying to do “def  
a(b)=©”. There’s other ways to accomplish the same purpose (e.g. def  
setter(range,value)) but I’m obsessed with the attribute writer form –  
which is just so dang cool.

Anyone have some light they could spare?

Argh. I’ve been struggling with this for a week!

---

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 6, 2006, 10:59am UTC](https://www.ruby-forum.com/t/attribute-writers/51727/2 "2006-01-06T10:59:07Z")

</div>

Michael J. wrote:

> Something like this appeals to me and is very readable:
> 
> if currentRespondent.ascii(0…2) == 123  
> currentRespondent.ascii(0…2) = 777  
> end
> 
> How would one actually implement this?

I’d suggest doing a little metaprogramming and making the final code  
a little easier.

Here is a very rough outline – if you like it I can help flesh out  
the details:

class Respondent

Fields = { :this =\> 0…2,  
:that =\> 3…6,  
:etc =\> 7…10 }

Fields.each\_pair do |field,range|  
# add a reader basically doing @resp[range]  
# add a writer basically doing @resp[range]=val  
end

def initialize(str)  
@resp = str  
end

end

resp = Respondent.new(whatever)  
if resp.this == Something  
resp.this = SomethingElse  
end  
do\_whatever if resp.that != resp.this

Make sense?

Hal

---

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 6, 2006, 11:05am UTC](https://www.ruby-forum.com/t/attribute-writers/51727/3 "2006-01-06T11:05:09Z")

</div>

On Fri, 06 Jan 2006 09:16:50 -0000, Michael J.  
[removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> Something like this appeals to me and is very readable:
> 
> if currentRespondent.ascii(0…2) == 123  
> currentRespondent.ascii(0…2) = 777  
> end
> 
> How would one actually implement this?

Is this any help?

```
irb(main):006:0> s = "123145992348241110111 11 11 1 1111 3331 1"
  => "123145992348241110111 11 11 1 1111 3331 1"
irb(main):007:0> s[0..2]
  => "123"
irb(main):008:0> s[0..2] = '777'
  => "777"
irb(main):009:0> s
  => "777145992348241110111 11 11 1 1111 3331 1"

```

> 

As far as I’m aware you can’t do indexed attribute writers. You can of  
course do:

```
def []=(idx, val)
  puts "at #{idx} = #{val}"
end

```

But Ruby won’t have it if you prefix the [] with a name.

What I think I’d do is just remove the writer (for this purpose) and  
have  
ascii return a String. The you can do:

```
if currentRespondent[0..2] == '777'
  currentRespondent.ascii[0..2] = "123"
end

```

(since String’s [] is pretty flexible as I showed above)

But I have a feeling I may have missed something in your question. Is  
that  
helpful?

---

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 6, 2006, 11:11am UTC](https://www.ruby-forum.com/t/attribute-writers/51727/4 "2006-01-06T11:11:10Z")

</div>

Michael J. wrote:

> I’m working on a survey system in Ruby and am having a style crisis with  
> a simple bit of code that has left me paralyzed and unable to work.

Here is the rest of my solution.

Does this help?

Hal

class Respondent

Fields = { :this =\> 0…2,  
:that =\> 3…6,  
:etc =\> 7…10 }

Fields.each\_pair do |field,range|  
# add a reader basically doing @resp[range]  
define\_method(field) { @resp[range] }  
# add a writer basically doing @resp[range]=val  
define\_method((field.to\_s+"=").to\_sym) do |val|  
raise “Wrong field length” if val.size != range.to\_a.size  
@resp[range] = val  
end  
end

def initialize(str)  
@resp = str  
end

end

resp = Respondent.new(“abcdefghij”)

puts resp.this  
puts resp.that  
puts resp.etc

resp.that = “foob”

puts resp.this  
puts resp.that  
puts resp.etc

resp.that = “foo” # error!

---

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 8, 2006, 10:52pm UTC](https://www.ruby-forum.com/t/attribute-writers/51727/5 "2006-01-08T22:52:42Z")

</div>

I want to say thanks for everyone’s responses. It took me a couple days  
to go through them all and it helped me learn quite a bit.

- Robert’s example with Enumerable was neat.
- Hal’s use of symbols was cool and interesting.
- Ross’s use of a simple string probably made the most sense for my  
situation

In the end I decided that simplest was best and subclassed String for  
each respondent: adding save, load, and changed? methods.

respondents.each do |respondent|  
respondent.open  
respondent[0…2] = 213  
respondent.close  
end

I’ve been struggling with object oriented programming for years with  
Perl, and only now (with Ruby) do I feel like I’m starting to really  
“get” it. It’s all about creating an interface that makes sense to use  
from a larger perspective. The object hides the gritty details.

Like with the above interface, I could load the respondent from memory,  
from database, or disk and it wouldn’t matter at all to the rest of the  
program. That’s sexy and makes my life easier.

Thanks again for everyone’s help. I certainly became a better  
programmer after playing with your examples.

Regards,

Mike Judge

---

<div class="post-metadata">

**Author:** ![bluetrust](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/8e7dd6/32.png) [@bluetrust](https://www.ruby-forum.com/u/bluetrust)\
**Post date:** [January 6, 2006, 11:35am UTC](https://www.ruby-forum.com/t/attribute-writers/51727/6 "2006-01-06T11:35:11Z")

</div>

Michael J. wrote:

> data string. In the stats and analysis section of my program, I’m
> 
> currentRespondent = Respondent.new
> 
> This is broken code, as Ruby doesn’t like me trying to do “def  
> a(b)=©”. There’s other ways to accomplish the same purpose (e.g.  
> def setter(range,value)) but I’m obsessed with the attribute writer  
> form – which is just so dang cool.
> 
> Anyone have some light they could spare?
> 
> Argh. I’ve been struggling with this for a week!

Hm, I wouldn’t use a string internally at all - at least not if you are  
trying to do it OO. This is what I’d do:

class Results  
include Enumerable

def initialize(from\_string = “”)  
@fields = from\_string.split(/\s+/).map {|s| s.to\_i}  
end

def field() @fields end

def each(&b)  
@fields.each(&b)  
self  
end

def size() @fields.size end  
def empty?() @fields.empty? end

def to\_str  
@fields.map {|d| d.to\_s}.join ’ ’  
end

alias :to\_s :to\_str

def to\_a() @fields end  
end

You could also simply subclass Array and create appropriate constructors  
and to\_s, to\_str methods:

class Results \< Array  
def initialize(from\_string = “”)  
super( from\_string.split(/\s+/).map! {|s| s.to\_i} )  
end

def to\_str  
map {|d| d.to\_s}.join ’ ’  
end

alias :to\_s :to\_str  
end

HTH

Kind regards

```
robert
```
