What's the Nature of "Property" in a Ruby Class

Why use Ruby's attr_accessor, attr_reader and attr_writer?

You may use the different accessors to communicate your intent to someone reading your code, and make it easier to write classes which will work correctly no matter how their public API is called.

class Person
attr_accessor :age
...
end

Here, I can see that I may both read and write the age.

class Person
attr_reader :age
...
end

Here, I can see that I may only read the age. Imagine that it is set by the constructor of this class and after that remains constant. If there were a mutator (writer) for age and the class were written assuming that age, once set, does not change, then a bug could result from code calling that mutator.

But what is happening behind the scenes?

If you write:

attr_writer :age

That gets translated into:

def age=(value)
@age = value
end

If you write:

attr_reader :age

That gets translated into:

def age
@age
end

If you write:

attr_accessor :age

That gets translated into:

def age=(value)
@age = value
end

def age
@age
end

Knowing that, here's another way to think about it: If you did not have the attr_... helpers, and had to write the accessors yourself, would you write any more accessors than your class needed? For example, if age only needed to be read, would you also write a method allowing it to be written?

Is it good that everything is an object?

A counterexample would be that in Java Integer is an object but int is not, which means different operations apply to both (admittedly in recent Java there is automatic conversion to/from the object version, but this can introduce unexpected performance issues). Objects are a little slower due to indirection, but more flexible; everything being an object means everything behaves consistently. Again Java would be an example: an array is not an object, and ArrayIterator is something that is bolted on after the fact (with multiple third party implementations, even) and therefore not quite consistent with the way collection class iterators work.

Intitalizing Object with Array of objects from another class Ruby

Your problem is assigning a new value to @stars_array variable on each iteration. There are multiple ways to deal with it:

@stars_array = (0..99).map { |i| Star.new('unknown_star',i) }

By the way, there is a couple of design issues (just for your attention):

  1. Why variable is called stars_array, not just stars?

  2. Why would ever instance of Star class have some object named @star inside? Recursion? :) Seems like @name would be proper and more clear attribute's name.

  3. Don't miss indentation.


EDIT: About DB-mapping. Most common way - inherit both classes from ActiveRecord::Base, and create one-to-many relation from solar system to stars. Each class will have it's own table. Takes absolutely no efforts.

best way to set a default value on a property without ActiveRecord?

class Foo
# class-level instance variable

# setting initial value (optional)
@class_var = 42

# making getter/setter methods on the class itself
class << self
attr_accessor :class_var
end

# instance-level variable getter/setter methods
attr_accessor :inst_var
def initialize
# setting initial value (optional)
@inst_var = 17
end
end

p Foo.class_var
#=> 42
Foo.class_var = 99
p Foo.class_var
#=> 99

f1 = Foo.new
f2 = Foo.new
f2.inst_var = 1
p [f1.inst_var, f2.inst_var]
#=> [17,1]

Object memory allocation

The answer depends on the actual implementation. Here I assume you are asking about MRI.

Ruby objects are allocated on the heap. There is no concept of the stack when talking about object allocations.

The heap is split up into pages, each consisting of 16kb. Each page is carved up into fixed size slots which can hold Ruby objects. A page can hold ~408 objects, since each object (which is an RVALUE struct) occupies 40bytes.

All of this is managed by the VM (ie. YARV).

ruby heap layout
source: http://timetobleed.com/garbage-collection-slides-from-la-ruby-conference/

Regarding your example, variables just hold references to objects, so m actually points to an allocated MyClass object.

The C struct (RClass) that backs up MyClass internally, contains a pointer to a table with the user-defined methods like #mySecondMethod and a pointer to a table with the names of the instance variables that its objects have.

Each object (which is backed up by RObject since the Object class is the default root of all objects) internally contains a pointer to the values of its instance variables.

The newly defined #mySecondMethod is available because of the dynamic nature of the language and the fact that method lookup happens at runtime.

Determining type of an object in ruby

The proper way to determine the "type" of an object, which is a wobbly term in the Ruby world, is to call object.class.

Since classes can inherit from other classes, if you want to determine if an object is "of a particular type" you might call object.is_a?(ClassName) to see if object is of type ClassName or derived from it.

Normally type checking is not done in Ruby, but instead objects are assessed based on their ability to respond to particular methods, commonly called "Duck typing". In other words, if it responds to the methods you want, there's no reason to be particular about the type.

For example, object.is_a?(String) is too rigid since another class might implement methods that convert it into a string, or make it behave identically to how String behaves. object.respond_to?(:to_s) would be a better way to test that the object in question does what you want.

Can I use an instance method for a computed value in a rails model?

The problem is that you're trying to access an ActiveRecord attribute with the assumption that each data field is stored in an instance variable. This doesn't work as attributes are stored in @attributes as a ActiveRecord::AttributeSet structure.

You can take a look at the structure using segment.instance_method_get(:@attributes)

Manipulating the raw data within this structure is pretty cumbersome. So AR defines attribute methods for every attribute in the class. In your case, #min, #min= or #sec, #sec= (and a bunch of other methods).

It's good practice to access data through accessor methods instead of accessing the instance variable directly. This holds true whether you're calling the method from inside or outside the class.

If you're calling the method outside the class, you need to explicitly state the receiver of the accessor method, which will be your segment object.

segment.min

Within your class, the receiver is implicitly defined as self (as Michael Kohl mentioned), so you can call the accessor directly using

class Segment
def padded_min
min to_s.rjust(2,"0")
end
end

Implicit definition works a bit differently for methods which end with = - Ruby assumes you're creating a local variable. You need to explicitly define the receiver if you're trying to call a setter method.

class Segment
def update_time(time)
self.time = (time)
end
end


Related Topics



Leave a reply



Submit