Forum: Ruby on Rails DRY in Models

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.
Mark D. (Guest)
on 2006-01-25 17:50
I am building an app right now that needs to grant access to three
levels of members right now - each will have their own table in the DB.
When creating the add_user action I am converting the password into a
hashed password through the model.

The way I am doing this right now, I will inevitably end up with
repeated code in three different models.  Is there a way I can define
this code in one single place and simply reference it from the models?

Sorry if this is a real newbie question - this is my first solo Ruby
app...
Ben R. (Guest)
on 2006-01-25 17:59
(Received via mailing list)
Mark ~

You can do this with One Table in your database using Single Table
Inheritance (STI).  You will have 4 models in the end, the base member
class, and then the 3 types of members which inherit from the member
class.
You can do this in Rails by adding a field named "type" to your member
table.  I believe the Wiki has more information on STI.

~ Ben
Mark D. (Guest)
on 2006-01-25 18:10
Ben R. wrote:
> Mark ~
>
> You can do this with One Table in your database using Single Table
> Inheritance (STI).  You will have 4 models in the end, the base member
> class, and then the 3 types of members which inherit from the member
> class.
> You can do this in Rails by adding a field named "type" to your member
> table.  I believe the Wiki has more information on STI.
>
> ~ Ben

Isn't there a way to do it using three models and three tables?  I'd
like to keep the data separate as I am holding different information
about each - I'd rather not have one table that asks for the address of
the admin (eventually me) - kind of pointless...

I'll look into STI - that seems close to what I am looking for.  I guess
ultimately what I am looking for is the equivalent of application.rb,
but for models rather than a controller...
Alex Y. (Guest)
on 2006-01-25 18:11
(Received via mailing list)
Ben R. wrote:
> Mark ~
>
> You can do this with One Table in your database using Single Table
> Inheritance (STI).  You will have 4 models in the end, the base member
> class, and then the 3 types of members which inherit from the member
> class.  You can do this in Rails by adding a field named "type" to your
> member table.  I believe the Wiki has more information on STI.

Really?  Sounded more like a modules question to me...

Mark, try this (with your own class names, of course):

module MyUser
   def hash_password
     do_stuff_here
   end
end
(you can put that in a file in lib/, and require it in environment.rb)

class User < ActiveRecord::Base
   include MyUser
   # User-specific methods go here
end
(this should be app/user.rb)

class Admin < ActiveRecord::Base
   include MyUser
   # Admin-specific methods go here
end
(this should be app/admin.rb)

class Member < ActiveRecord::Base
   include MyUser
   # Member-specific methods go here
end
(this should be app/member.rb)

That way there's no restriction on the differences in table schema
between the user classes.

--
Alex
Guido S. (Guest)
on 2006-01-25 18:15
(Received via mailing list)
Take a look at Single Table Inheritance especially if your members
are very similar ... STI has some limitations so you should check to
make sure that it won't adversely affect you.

See

	http://www.ruby-forum.com/topic/51386
	http://wiki.rubyonrails.com/rails/pages/SingleTabl...

If you're feeling adventurous, you may want to check out Class Table
Inheritance ...

	http://johnwilger.com/articles/2005/09/29/class-ta...
rails-with-postgresql

but it seems a little too much trouble for me right now. I like
things to be as simple as possible.

-- G.
Mark D. (Guest)
on 2006-01-25 18:21
Alex Y. wrote:
> Ben R. wrote:
>> Mark ~
>>
>> You can do this with One Table in your database using Single Table
>> Inheritance (STI).  You will have 4 models in the end, the base member
>> class, and then the 3 types of members which inherit from the member
>> class.  You can do this in Rails by adding a field named "type" to your
>> member table.  I believe the Wiki has more information on STI.
>
> Really?  Sounded more like a modules question to me...
>
> Mark, try this (with your own class names, of course):
>
> module MyUser
>    def hash_password
>      do_stuff_here
>    end
> end
> (you can put that in a file in lib/, and require it in environment.rb)
>
> class User < ActiveRecord::Base
>    include MyUser
>    # User-specific methods go here
> end
> (this should be app/user.rb)
>
> class Admin < ActiveRecord::Base
>    include MyUser
>    # Admin-specific methods go here
> end
> (this should be app/admin.rb)
>
> class Member < ActiveRecord::Base
>    include MyUser
>    # Member-specific methods go here
> end
> (this should be app/member.rb)
>
> That way there's no restriction on the differences in table schema
> between the user classes.
>
> --
> Alex

Alex,

I think that was exactly what I was looking for!  I'll give that a try.
I'm working with the first table right now (trying to get everything
working correctly), but will let you know if this worked.

Thank you everyone for your responses so far.
Ajai Khattri (Guest)
on 2006-01-25 18:24
(Received via mailing list)
Mark D. wrote:

>The way I am doing this right now, I will inevitably end up with
>repeated code in three different models.  Is there a way I can define
>this code in one single place and simply reference it from the models?
>

Sounds like that code should be in a helper...


--
Ben R. (Guest)
on 2006-01-25 18:24
(Received via mailing list)
Hi Mark ~

Yep it is a model question, but in the end the models will relate to
some
physical database table.  I guess it depends on how different the 3
membership tables are to decide if they need their own tables.  I would
say
the DRYest (spelling?) way to do this would be with one table, STI, and
then
have support tables for different member types.

Just my two cents, your method works as well and does focus on the
models.

~ Ben
Guido S. (Guest)
on 2006-01-25 18:27
(Received via mailing list)
Perhaps you want something like this?

In my STI, I have entities, all of which have an address as well as a
contact person.

class Entity < ActiveRecord::Base
   composed_of :address,
               :class_name => Address,
               :mapping =>
                 [   # database            # ruby
                   [ :primary_address,    :primary     ],
                   [ :secondary_address,  :secondary   ],
                   [ :city,               :city        ],
                   [ :region,             :region      ],
                   [ :postal_code,        :postcode    ],
                 ]
   composed_of :contact,
               :class_name => Contact,
               :mapping =>
                 [   # database            # ruby
                   [ :phone_number,       :phone     ],
                   [ :mobile_number,      :mobile    ],
                   [ :fax_number,         :fax       ],
                   [ :email_address,      :email     ]
                 ]
end

Then I use aggregations to 'wrap' some of the columns into something
that makes more sense within the application domain ...

class Address
   attr_reader :primary, :secondary, :city, :region, :postcode

   def initialize(primary, secondary, city, region, postcode)
     @primary = primary
     @secondary = secondary
     @city = city
     @region = region
     @postcode = postcode
   end

   def to_s
     "#{@primary}, @ #{@city}"
   end
end

class Contact
   attr_reader :phone, :mobile, :fax, :email

   def initialize(phone, mobile, fax, email)
     @phone = phone
     @mobile = mobile
     @fax = fax
     @email = email
   end

   def to_s
     "M: #{@mobile} T: #{@phone} F: #{@fax} E: #{@email}"
   end
end

And the human class, gets everything defined in Entity for free ...

class Human < Entity
   composed_of :name,
               :class_name => Name,
               :mapping =>
                 [   # database    # ruby
                   [ :first_name,  :first  ],
                   [ :middle_name, :middle ],
                   [ :last_name,   :last   ],
                   [ :description, :descr  ]
                 ]
end

As does each Organization

class Organization < Entity
   def location
     "#{self.city}, #{self.region}"
   end
end

Hope this helps ...

-- G.
Dylan S. (Guest)
on 2006-01-25 18:30
(Received via mailing list)
I have something similar setup, and since the data the users contain is
so
unique, I opted for three tables.

Also, I have a login page for each of these users, that need to be
accessed
by different URL's.
I end up having to use similar code for each of the login pages, views,
controllers....

I know this is a prime candidite for refactoring to adhere to the DRY
principles... however, I'm more of a IRM kind of guy now.
( I Repeat Myself).
DeLynn B. (Guest)
on 2006-01-25 19:30
(Received via mailing list)
Mark,

I'll go ahead and throw in my two cents on this discussion as well.
;-) I feel that the two questions that needs to be answered when
deciding whether to use STI or a library when DRYing up your models
are:

1. How similar are the models that are going to use the STI
functionality?
2. How reusable is the functionality that is going into a library in
terms of your entire application?

So, to relate this back to your current situation, I think you have to
decide if your "members" are similar enough objects to warrant STI,
and you also have to decide if the encryption method that is similar
to your "members" might also be used elsewhere in your application.

> Isn't there a way to do it using three models and three tables?  I'd
> like to keep the data separate as I am holding different information
> about each - I'd rather not have one table that asks for the address of
> the admin (eventually me) - kind of pointless...

I would first ask: how different is this information from member to
member? Are we talking lots of similar attributes (like username,
password, email, etc) and then a handful of dis-similar attributes
(like address for some not for others, salary for some and not for
others, etc.)? Or do you have a situation where the different members
might only share a handful of similar items and then have a multitude
of dis-similar ones?

I have little mantra that I try to remind myself with whenever I'm
dealing with users in an application and that is:"a User is a User is
a User". In my experience I have found that when dealing with users,
you usually have a common set of data that you are collecting for each
user regardless of their type or role in the application. Because of
this I usually create a common users table and then use STI to create
the different "types" of user. Then, for the types that require
dis-similar attributes I create tables to store that information and
then use a belongs_to relationship. This ensures that I don't add
unnecessary attributes to tables that might not get used with all my
users while giving me the flexibility to "extend" my user whenever I
need to collect additional information about a different type of user
by simply creating a new table.

One thing you have to keep in mind when creating multiple tables for
similar objects is that every time you have to add an attribute to
your many objects you'll have to add it in multiple tables instead of
only having to add it to one table.

All that said, maybe you might need the ability to "encrpt" attributes
of other objects in a similar fashion somewhere else in your
application. If this is the case, then adding that method to a library
and then including it in whatever models might need it is definitely
the way to go.

You could even do a combination of the two and do STI for the
members/users stuff and still pull the encryption method out into a
library and then include it into your base member/user object and
whatever other objects might need that functionality in your
application.

Sorry for the long winded post, but hopefully this adds a bit of
perspective to the two approaches listed.

--
DeLynn B.
removed_email_address@domain.invalid
http://www.delynnberry.com
Mark D. (Guest)
on 2006-01-25 19:42
DeLynn,

Thank you for your response...definitely some food for thought.  The
code that I want shared across the models is nothing more than a
password encrypting code.  I think using a library for this might be the
way to go ultimately.

The idea of extending dis-similar attributes of a user into a new table
is interesting - I've never thought of doing it this way before.  I'm
not sure how much I would like that, though.  I tend to be a bit of a
simpleton, and having well-defined tables that say what they are and are
self-contained just seems a bit easier to me.

I definitely agree, however, with the idea that if the users are
similar, a single table would be the best way to go.  Unfortunately,
with the three types of users, the only thing that they share is an id,
username, and password.  Beyond that, I am collecting different
information...

Mark D.
DeLynn B. (Guest)
on 2006-01-25 19:58
(Received via mailing list)
> Thank you for your response...definitely some food for thought.
> The idea of extending dis-similar attributes of a user into a new table
> is interesting - I've never thought of doing it this way before.

Not a problem. Glad I could provide you with some brain food! :-)

>  I'm not sure how much I would like that, though.  I tend to be a bit of a
> simpleton, and having well-defined tables that say what they are and are
> self-contained just seems a bit easier to me.

There is definitely something to be said for keeping something simple
when simple will do. My experience is mostly with internal, large
scale, multi-user applications where I am collecting lots of similar
things for my users (like > 30 pieces of data) and then also have
other belongs_to attributes for the multitudes of other data I need to
keep track of (multiple addresses, multiple phone numbers, multiple
email addresses, etc). Personally I've found that managing small
packaged tables is easier on my brain (I really hate to hoziontally
scroll my tables ;-).

> Unfortunately, with the three types of users, the only
> thing that they share is an id, username, and password.
> Beyond that, I am collecting different information...

If this is the case, then using the library is probably a much easier
route to take. You're absolutely making the right decision.

--
DeLynn B.
removed_email_address@domain.invalid
http://www.delynnberry.com
This topic is locked and can not be replied to.