[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Newbie Question

Dick

3/1/2007 12:16:00 PM

Hi,

I am new to Ruby. This is the very first class I am writing and it
doesn't seem that Ruby is designed to handle what I thought should work.
Why doesn't the below work?

class Song < ActiveRecord::Base
@@dcounts = []

def Song.count(difficulty, artistid)
@@dcounts[difficulty][artistid]
end

def Song.count=(difficulty, artistid, val)
@@dcounts[difficulty] ||= {}
@@dcounts[difficulty][artistid] = val
end
end

Song.count(1, 3547) = 10

test.rb:14: parse error, unexpected '=', expecting $
Song.count(1,3547) = 10
^

--
Posted via http://www.ruby-....

22 Answers

Jano Svitok

3/1/2007 1:17:00 PM

0

On 3/1/07, Dick <rwk@americom.com> wrote:
> Hi,
>
> I am new to Ruby. This is the very first class I am writing and it
> doesn't seem that Ruby is designed to handle what I thought should work.
> Why doesn't the below work?
>
> class Song < ActiveRecord::Base
> @@dcounts = []
>
> def Song.count(difficulty, artistid)
> @@dcounts[difficulty][artistid]
> end
>
> def Song.count=(difficulty, artistid, val)
> @@dcounts[difficulty] ||= {}
> @@dcounts[difficulty][artistid] = val
> end
> end
>
> Song.count(1, 3547) = 10
>
> test.rb:14: parse error, unexpected '=', expecting $
> Song.count(1,3547) = 10

You can't have more then one argument to setter (var=)

change it to

def Song.set_count(difficulty, artistid, val)
@@dcounts[difficulty] ||= {}
@@dcounts[difficulty][artistid] = val
end

Song.set_count(1, 3547,10)

Then, maybe you can create classes for each of the subhashes to
encapsulate the functionality - but it depends on the purpose and the
data.

It's possible to write the code that you could write:

Song.difficulty(1).artist(3547) = 10

Brian Candler

3/1/2007 1:23:00 PM

0

On Thu, Mar 01, 2007 at 09:15:40PM +0900, Dick wrote:
> I am new to Ruby. This is the very first class I am writing and it
> doesn't seem that Ruby is designed to handle what I thought should work.
> Why doesn't the below work?
>
> class Song < ActiveRecord::Base
> @@dcounts = []
>
> def Song.count(difficulty, artistid)
> @@dcounts[difficulty][artistid]
> end
>
> def Song.count=(difficulty, artistid, val)
> @@dcounts[difficulty] ||= {}
> @@dcounts[difficulty][artistid] = val
> end
> end
>
> Song.count(1, 3547) = 10
>
> test.rb:14: parse error, unexpected '=', expecting $
> Song.count(1,3547) = 10
> ^

Unfortunately, a 'foo=' method can only take one argument, which is the
expression to the right of the equals sign.

You can create a regular method, e.g.

def Song.set_difficulty(difficulty, artistid, val)
...
Song.set_difficultly(1, 3547, 10)

However I wonder if perhaps you're going about this the wrong way. You're
using a class variable, @@dcounts, which implies that you plan to create
multiple instances of class 'Song'. So maybe what you really need is an
accessor on the individual Song object to set its difficulty, which could
also update your @@dcounts index for you.

Also, consider having a separate SongIndex class, to which individual Songs
are added. At the time when you add them (or at some later time) you can
index its difficulty.

This approach is cleaner because you get away from the class variable; you
can just use a regular instance variable of the SongIndex class. It is also
more flexible - it allows you to create individual instances of Song which
are not in the global index.

HTH,

Brian.

Brian Candler

3/1/2007 2:12:00 PM

0

On Thu, Mar 01, 2007 at 10:22:39PM +0900, Brian Candler wrote:
> > class Song < ActiveRecord::Base
> > @@dcounts = []
> >
> > def Song.count(difficulty, artistid)
> > @@dcounts[difficulty][artistid]
> > end
> >
> > def Song.count=(difficulty, artistid, val)
> > @@dcounts[difficulty] ||= {}
> > @@dcounts[difficulty][artistid] = val
> > end
> > end
...
> However I wonder if perhaps you're going about this the wrong way. You're
> using a class variable, @@dcounts, which implies that you plan to create
> multiple instances of class 'Song'. So maybe what you really need is an
> accessor on the individual Song object to set its difficulty, which could
> also update your @@dcounts index for you.

Are you trying to count, for each artist, the number of songs of a
particular difficulty level? Then you can make the the difficulty an
attribute of the song:

class Song
@@dcounts = []

def initialize(artistid, name, diff=nil)
@artistid = artistid
@name = name
self.difficulty=(diff)
end

def difficulty=(diff)
# decrement count at old difficulty (if any)
if @difficulty
@@dcounts[@difficulty][@artistid] -= 1
end

# increment count at new difficulty
if diff
@@dcounts[diff] ||= {}
@@dcounts[diff][@artistid] += 1
end

# remember difficulty
@difficulty = diff
end
end

However if you are doing this with ActiveRecord, i.e. you have a SQL
database on the backend, then I'd forget the class variable and let the
database do the work for you. Store the difficulty as an attribute of the
song, then you just need something like (untested)

def Song.count(difficulty, artistid)
Song.count_by_sql([
"SELECT COUNT(*) FROM songs WHERE id=? AND difficulty=?",
artistid, difficulty])
end

Or using :group conditions you could get all the songs grouped by difficulty
and artistid, effectively populating your @@dcounts variable in one go. I'm
I'm not sure if AR provides a method which will accept the output from a
SELECT foo,COUNT(*) GROUP BY foo, but at worst you can send it directly to
the underlying database.

HTH,

Brian.

Tim Becker

3/1/2007 2:30:00 PM

0

>You can't have more then one argument to setter (var=)

oddly enough, you can define such a method, but you can't call it
without jumping through some hoops:

irb(main):006:0> class Test
irb(main):007:1> def set= a,b
irb(main):008:2> @a=a
irb(main):009:2> @b=b
irb(main):010:2> end
irb(main):011:1> end
irb(main):012:0> t = Test.new
=> #<Test:0x40205cac>
irb(main):013:0> t.set=1,2
ArgumentError: wrong number of arguments (1 for 2)
...
irb(main):016:0> t.send "set=", 1, 1
=> 1
irb(main):017:0> t
=> #<Test:0x40205cac @b=1, @a=1>


Weird behaviour,
-tim

Rick DeNatale

3/1/2007 5:37:00 PM

0

On 3/1/07, Dick <rwk@americom.com> wrote:
> Hi,
>
> I am new to Ruby. This is the very first class I am writing and it
> doesn't seem that Ruby is designed to handle what I thought should work.
> Why doesn't the below work?
>
> class Song < ActiveRecord::Base
> @@dcounts = []
>
> def Song.count(difficulty, artistid)
> @@dcounts[difficulty][artistid]
> end

You've already stimulated discussion on the restrictions on x= methods in Ruby.

Since you say this is your first ruby class, I feel the need to
critique the use of class variables.

While there might be some uses of class variables, I tend to see a
little red light go off every time I see @@ in ruby code. It's not
likely that a class variable will do what the programmer thinks it
does, since class variables are shared between classes and subclasses.
In most cases what you really want are class INSTANCE variables which
look just like instance variables except that they appear in the
context of a class method.

I'd rewrite that beginning code as something like

class Song < ActiveRecord::Base
# This defines an accessor for the dcounts class instance variable
# note that this is a class method
# it could also have been defined as def self.dcounts, but I usually
# prefer naming the class as a matter of style.
def Song.dcounts
# if @dcounts is nil initialize it to an empty array
# in any case return it.
# This is a standard ruby idiom for lazy initialization
@dcounts ||= []
end

# Another class method, I used the accessor so that
# @dcounts will be properly intialized, other references should
# do the same.
def Song.count(difficulty, artistid)
dcounts[difficulty][artistid]
end
...

HTH

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...

james.d.masters

3/1/2007 6:17:00 PM

0

On Mar 1, 9:37 am, "Rick DeNatale" <rick.denat...@gmail.com> wrote:
> ...
> While there might be some uses of class variables, I tend to see a
> little red light go off every time I see @@ in ruby code. It's not
> likely that a class variable will do what the programmer thinks it
> does, since class variables are shared between classes and subclasses.
> ...

This is one thing that really bothers me about Ruby. Does anyone know
if this will be addressed in further Ruby versions? The class
instance variable to me is a bit of a "work around" and I'd like to
see a more elegant way to have class variables that are inherited into
sub-classes, yet the sub-class version does not point directly to the
exact same object (i.e. same memory space) as parent class.

I can see the use for "@@" as it works now; however, the usage would
be far less than a class instance variable (at least for me).

Gary Wright

3/1/2007 7:36:00 PM

0


On Mar 1, 2007, at 1:20 PM, james.d.masters@gmail.com wrote:
> This is one thing that really bothers me about Ruby. Does anyone know
> if this will be addressed in further Ruby versions? The class
> instance variable to me is a bit of a "work around" and I'd like to
> see a more elegant way to have class variables that are inherited into
> sub-classes, yet the sub-class version does not point directly to the
> exact same object (i.e. same memory space) as parent class.

I think you are asking for something which is quite easy:

class A
class <<self
attr_accessor :per_class
end
end

class B < A
end

A.per_class = 1
B.per_class = 2
puts A.per_class # 1
puts B.per_class # 2

Is this what you are looking for or something else?

Gary Wright




james.d.masters

3/1/2007 8:18:00 PM

0

On Mar 1, 11:35 am, Gary Wright <gwtm...@mac.com> wrote:
> ...
> I think you are asking for something which is quite easy:
>
> class A
> class <<self
> attr_accessor :per_class
> end
> end
> ...

Yes, this is kind of what I'm referring to; however, my gripe is that
this is somewhat esoteric. The beauty of other parts of Ruby is that
the code is terse and readable. Also, I think that the work-around
that you mentioned (and I've used too) does not truly inherit:

class A
class <<self
attr_accessor :per_class
end
end

A.per_class #=> 1

class B < A
end

B.per_class #=> nil

Something that is unique to a class but inherits with nice syntax is
what I'd like to see (and I'm fairly sure is the behavior used in
other OOP languages). For example, assume that the behavior that I'm
referring to uses a triple "@" prefix to initialize ("@@@"). Then:

class A
@@@per_class = 1
attr_accessor :per_class
end

A.per_class #=> 1

class B < A
end

B.per_class #=> 1
B.per_class = 2 #=> 2
A.per_class #=> 1

I've read about this in postings before; however, I'm not aware of any
efforts made to support this behavior natively. I'm hoping that this
feature is being considered or hopefully worked on. I've had the need
for this on more than one occasion.

Thanks,
-James

Brian Candler

3/1/2007 9:13:00 PM

0

On Fri, Mar 02, 2007 at 05:20:11AM +0900, james.d.masters@gmail.com wrote:
> Something that is unique to a class but inherits with nice syntax is
> what I'd like to see (and I'm fairly sure is the behavior used in
> other OOP languages). For example, assume that the behavior that I'm
> referring to uses a triple "@" prefix to initialize ("@@@"). Then:
>
> class A
> @@@per_class = 1
> attr_accessor :per_class
> end
>
> A.per_class #=> 1
>
> class B < A
> end
>
> B.per_class #=> 1
> B.per_class = 2 #=> 2
> A.per_class #=> 1

So how would it work? B.per_class and A.per_class are clearly different,
since they end up pointing at different objects. Does the fact that
@@@per_class is undefined in B mean that the value in A is used?

In that case, you need something like a class hierarchy search to find the
instance variable. How about this:

class A
def self.per_class
self.ancestors.each { |k|
k.instance_eval { return @per_class if defined? @per_class }
}
end
def self.per_class=(x)
@per_class=x
end
end

puts "hello"
A.per_class = 1

class B<A
end

puts A.per_class # 1
puts B.per_class # 1
B.per_class = 2
puts A.per_class # 1
puts B.per_class # 2

Clearly, this could be wrapped up as

class_attr_accessor :per_class

or similar.

If this gets rid of the @@ syntax from the language, then I'm in favour :-)

Regards,

Brian.

Rick DeNatale

3/1/2007 9:16:00 PM

0

On 3/1/07, james.d.masters@gmail.com <james.d.masters@gmail.com> wrote:
> On Mar 1, 11:35 am, Gary Wright <gwtm...@mac.com> wrote:
> > ...
> > I think you are asking for something which is quite easy:
> >
> > class A
> > class <<self
> > attr_accessor :per_class
> > end
> > end
> > ...
>
> Yes, this is kind of what I'm referring to; however, my gripe is that
> this is somewhat esoteric. The beauty of other parts of Ruby is that
> the code is terse and readable.

Go back and look at my earlier post.

Just what does @var mean?

In a method it means that this is an instance variable of the object
executing the method.

rick@frodo:/public/rubyscripts$ cat civ.rb
class C

def instance_var
@instance_var
end

def instance_var=(val)
@instance_var = val
end

def C.class_instance_var
@class_instance_var
end

def C.class_instance_var=(val)
@class_instance_var = val
end
end

class D < C
end

c = C.new
c1 = C.new

c.instance_var = 'c'
c1.instance_var = 'c1'

puts "c.instance_var=#{c.instance_var}"
puts "c1.instance_var=#{c1.instance_var}"

C.class_instance_var="C"
puts "C.class_instance_var=#{C.class_instance_var}"
puts "D.class_instance_var=#{D.class_instance_var}"
D.class_instance_var="D"
puts "C.class_instance_var=#{C.class_instance_var}"
puts "D.class_instance_var=#{D.class_instance_var}"
rick@frodo:/public/rubyscripts$ ruby civ.rb
c.instance_var=c
c1.instance_var=c1
C.class_instance_var=C
D.class_instance_var=
C.class_instance_var=C
D.class_instance_var=D
rick@frodo:/public/rubyscripts$

All a class instance variable is, is an instance variable of the
class. so @class_inst_var is a class instance variable of C because
it's in class methods of C.

Gary's example looks a bit esoteric perhaps, but the attr_accessor
method is just a metamethod which generates the two methods for each
of my variables. My code could be replaced with the equivalent:

class C

attr_accessor :instance_var

class << C # or class << self
attr_accessor :class_instance_var
end

end

class D < C
end

The class<<self (within the definition of C) or class << C (which
could also be outside) simply puts us in a context where self is the
class C, which allows us to invoke the private method attr_accessor.

To put this in the context of another older OO language, Smalltalk has
instance variables, class instance variables and class variables, but
unlike in ruby the existance of these has to be declared, with
something like

Object subclass: #C
instanceVariables: 'instanceVariable'
classInstanceVariables: 'classInstanceVariable'
classVariables: 'ClassVariable'

The difference between Ruby and Smalltalk springs from Matz' decision
to use sigils* to differentiate between the variable types rather than
requiring declaration.

* http://en.wikipedia.org/wiki/Sigil_%28computer_prog...

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denh...