Accessing 'many' variables


#1

Hi,

I have a class called Outlet which has_many Jacks. Apart from internal
id, each jack is unique by the attribute ‘color’.

I’d like to iterate through each Outlet, and be able to output data
for a Jack of a certain color.

Say, I want to access the orange jack for the current outlet:

<%=h outlet.jack.color['O'].ip_address %>

undefined method `color’ for Jack:Class

But ‘color’ is an attribute/column in the Jack Class.

I can do a for loop inside an Outlet loop, but I want to dynamically
refer to each Jack inside Outlet by the color attribute of Jack.

Is this possible?

  • Nic

#2

has_many associations are pluralized, and I think
you have the Ruby syntax a bit wonky:

outlet.jacks.detect { |j| j.color == ‘O’ }.ip_address

Even better to add a method to Jack:

def is_orange? # assumes color == ‘O’ means orange
color == ‘O’
end

Then: outlet.jacks.detect { |j| j.is_orange? }.ip_address

Models should know about themselves, and be able to answer
for themselves. Better to hide the implementation within
the model.

Better yet would be to make the addition to Jack as described,
and add this to Outlet:

def orange_jack
jacks.detect { |j| j.is_orange }
end

and in the view:

outlet.orange_jack.ip_address

If you abstract your code this way, you’ll weep with joy when
you revisit it a few months or years from now. And, anybody
else who has to look at it will be similarly enthralled. :slight_smile:


– Tom M.


#3

Wow, thats beautiful, thanks for the help! I’m going to change some of
my code now.

One more thing: How would I handle dynamic jack colors? I have outlets
that contain n jacks with n colors.

I wouldn’t be able to refer to ‘orange_jack’, but I would want to
display/loop an outlet with all available jacks and their respective
colors.

My solution would be to take what you’ve done above, but create a more
generic method, and try to pass it the color value…doesn’t seem
clean though.

  • Nic.

On 2/10/06, Tom M. removed_email_address@domain.invalid wrote:

 jacks.detect { |j| j.is_orange }

Say, I want to access the orange jack for the current outlet:
Is this possible?
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails

  • Nic

#4

On Feb 10, 2006, at 5:28 PM, Nic W. wrote:

Wow, thats beautiful, thanks for the help! I’m going to change some of
my code now.

No problem. If you call my code beautiful, I’ll help anytime. :slight_smile:

One more thing: How would I handle dynamic jack colors? I have outlets
that contain n jacks with n colors.

The Right™ way to do this would be to have a separate table and model
with the colors in it…

I wouldn’t be able to refer to ‘orange_jack’, but I would want to
display/loop an outlet with all available jacks and their respective
colors.

outlet.jacks.each { |j| j.color.name }

My solution would be to take what you’ve done above, but create a more
generic method, and try to pass it the color value…doesn’t seem
clean though.

If there are a fixed number of colors, just create a method for each
one…

But…why pass in a color if you just want to list all available?
Perhaps for Jack#is_orange? replacement?

With tables as suggested above, like this:

outlet.jacks.first.is_color?(Color.find_by_name(‘orange’))

in Jacks:

def is_color?(match_color)
color.name == match_color.name
end

There is advanced (intermediate?) Ruby that I’m not 100% comfortable
with yet that allows trapping undefined method calls that would allow
automatic handling of:

  is_*? method calls dynamically.

Anyone care to enlighten us both? :slight_smile:


– Tom M.


#5

Hi –

On Fri, 10 Feb 2006, Tom M. wrote:

There is advanced (intermediate?) Ruby that I’m not 100% comfortable
with yet that allows trapping undefined method calls that would allow
automatic handling of:

is_*? method calls dynamically.

Anyone care to enlighten us both? :slight_smile:

The trapping way would involve method_missing, but that’s inadvisable
with an ActiveRecord::Base subclass, since method_missing is already
in use.

Another approach would be to have the Jack class create the methods
dynamically, based on what it could find out about the available
colors – something like:

class Jack
Color.find(:all).each do |c|
define_method(“is_#{c.name}?”) { color.name == c.name }
end

 # other code....

end

David


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! http://www.manning.com/books/black


#6

On Feb 10, 2006, at 6:27 PM, removed_email_address@domain.invalid wrote:

def is_color?(match_color)

The trapping way would involve method_missing, but that’s inadvisable
with an ActiveRecord::Base subclass, since method_missing is already
in use.

Ah…

Could you chain them with super? i.e. (pseudo code to follow)

def method_missing
begin
super # let ActiveRecord take a shot
rescue
do local method missing magic
end
end

end
That works too.

Thanks a bunch!


– Tom M.


#7

Hi –

On Fri, 10 Feb 2006, Tom M. wrote:

in Jacks:

def method_missing
begin
super # let ActiveRecord take a shot
rescue
do local method missing magic
end
end

Yes, you could definitely add to the chain. (I’m willing to admit
that there’s an element of sheer superstition to my avoidance of
collaborating on method_missing with AR :slight_smile: You’d have to rescue
NoMethodError specifically, and I can’t quite puzzle through whether
there would still be a danger of over-eager rescuing…

Also, if you have a lot of things you want to trap, the local
method_missing could get unwieldy (which is one reason I tend not to
reach for method_missing as a general solution to missing methods,
though I probably should). But that probably wouldn’t happen too
often.

David


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! http://www.manning.com/books/black


#8

On Feb 10, 2006, at 10:12 PM, Ben M. wrote:

end
# other code....

end

Curious… when would the db call get made? Only when the class is
first loaded? Everytime one of the dynamically defined methods is
called?

Just when the class is loaded.

But…even if it was every time, worrying about before there was a
problem is worrying about the wrong thing…unless you’re getting
paid based upon how many requests per second the system can handle.


– Tom M.


#9

The Right™ way to do this would be to have a separate table and
model
with the colors in it…

I would respectfully disagree in this case. Putting these types of
constants in tables is a good time to think YAGNI.

You want to ask, over the lifetime of such an activity, how many
times would a user need to add a new color or edit a color? Almost
certainly less than 10^2. And when they do, they are probably going
to feel frustrated by the fact they have to go use an editor to add a
new color in order to add a jack with a new color. And if you hide
the process of creating the Color objects by creating them
automatically from whatever string they type in, you’ve defeated the
purpose.

Opposed to that you have the likely practical issue that the
constants will be used many places, and using them as db objects
directly becomes a performance issue and they have to be cached. Now
you have to think about / implement a caching system.

Then, when you’ve got it all working, the most likely change is not
that the classification system changes in a way that your abstraction
helps you deal with productively, but instead that the classification
system changes altogether in a way with respect to which your
abstractions are MORE brittle than just using a varchar column on the
object. All you need are some rudimentary mechanisms for keeping the
classifications consistent.

Rule of thumb that works for me: postpone abstracting collections of
fewer than 20 or so things that are adjectives rather than nouns and/
or that the user rarely CRUDs. At least until the app is mature
enough that you can count on the abstractions having some longevity,
or complex enough that the abstractions save you now time (as opposed
to future time).

This was a very hard lesson for me to learn, as I’m by nature one of
those abstract everything kind of programmers.

Michael J.


#10

removed_email_address@domain.invalid wrote:

  define_method("is_#{c.name}?") { color.name == c.name }
end

# other code....

end

Curious… when would the db call get made? Only when the class is first
loaded? Everytime
one of the dynamically defined methods is called?

very cool though…

b


#11

Very cool. So say the user wants to have the ‘orange’ appear first,
and ‘blue’ second. How would I insert the color name to call the
is_? method?

Same way you defined it? Pretending I have a array of colors
(user_color) that indicates the order of colors to be listed, would I
do:

is_${user_color[0]]}?

  • Nic.

On 2/11/06, Tom M. removed_email_address@domain.invalid wrote:

Color.find(:all).each do |c|

http://lists.rubyonrails.org/mailman/listinfo/rails

  • Nic

#12

Well, you’re right about the lack of changes. What happens is that
here are commonly three outlets, that need to be presented in a
certain order (B,O,G), but due to reality, not every jack has one of
those colors, or the schema may change.

I was thinking of having an Add New Jack function, whereupon the user
can select no more than 12 colors - you wouldn’t have any more shades
because then the jacks would look too similiar.

Right now I just have the Jack with a ‘color’ attribute which is the
‘O’ or ‘G’ and an html_color column. The ‘color’ column is needed
because the outlets full name has the color at the end, ie 032-B.

Thanks everyone for the help, I’m still sorting out the responses, but
now that we’re this far, the overall goal would be to have dynamic
colors, dynamically refer them, but be able to set preferences on the
order of the listing, ie B, O, G - not necessarily ascending or
descending.

  • Nic.

On 2/11/06, Michael J. removed_email_address@domain.invalid wrote:

You want to ask, over the lifetime of such an activity, how many
directly becomes a performance issue and they have to be cached. Now
Rule of thumb that works for me: postpone abstracting collections of


Rails mailing list
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails

  • Nic

#13

Hi –

On Sun, 12 Feb 2006, Nic W. wrote:

Very cool. So say the user wants to have the ‘orange’ appear first,
and ‘blue’ second. How would I insert the color name to call the
is_? method?

Same way you defined it? Pretending I have a array of colors
(user_color) that indicates the order of colors to be listed, would I
do:

is_${user_color[0]]}?

Not in Ruby :slight_smile: But I know what you mean:
jack.send(“is_#{user_color[0]}?”)

But you can get Ruby to do the sorting automatically for you. Let’s
say, to keep it simple, you have the colors in the right order stored
as strings (rather than Color objects). If you want to sort an array
of jacks according to their color order, you could do:

sorted_jacks = jacks.sort_by {|j| colors.index(j.color) }

If you have duplicate-colored jacks, or if there’s a color not
represented among the jacks, you’d have to do a little more… but
something like that should work.

You could also do it the other way around:

sorted_jacks = colors.map {|c| jacks.find {|j| j.color == c } }

or some variation on that.

David

with an ActiveRecord::Base subclass, since method_missing is already

  • Nic

Rails mailing list
removed_email_address@domain.invalid
http://lists.rubyonrails.org/mailman/listinfo/rails


David A. Black (removed_email_address@domain.invalid)
Ruby Power and Light (http://www.rubypowerandlight.com)

“Ruby for Rails” chapters now available
from Manning Early Access Program! http://www.manning.com/books/black