Forum: Ruby on Rails Accessing 'many' variables

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
Nic W. (Guest)
on 2006-02-10 22:27
(Received via mailing list)
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:

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

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
Tom M. (Guest)
on 2006-02-11 03:04
(Received via mailing list)
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. :-)

--
-- Tom M.
Nic W. (Guest)
on 2006-02-11 03:28
(Received via mailing list)
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
Tom M. (Guest)
on 2006-02-11 03:57
(Received via mailing list)
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. :-)

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

The Right(tm) 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? :-)

--
-- Tom M.
unknown (Guest)
on 2006-02-11 04:29
(Received via mailing list)
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? :-)

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
Tom M. (Guest)
on 2006-02-11 04:35
(Received via mailing list)
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.
unknown (Guest)
on 2006-02-11 05:27
(Received via mailing list)
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 :-)  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
Ben M. (Guest)
on 2006-02-11 08:10
(Received via mailing list)
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
Tom M. (Guest)
on 2006-02-11 12:58
(Received via mailing list)
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.
Michael J. (Guest)
on 2006-02-11 16:05
(Received via mailing list)
> The Right(tm) way to do this would be to have a separate table and
> model
> with the colors in it...

<big long discussion about abstracting small static collections into
db tables>

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.
Nic W. (Guest)
on 2006-02-13 06:36
(Received via mailing list)
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
Nic W. (Guest)
on 2006-02-13 06:42
(Received via mailing list)
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_<color here>? 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
unknown (Guest)
on 2006-02-13 15:04
(Received via mailing list)
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_<color here>? 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 :-)  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
This topic is locked and can not be replied to.