I'm trying to extend a model in rails.
Model User uses table users in database with field :username, :password.
class User < ActiveRecord::Base
end
Model SuperUser uses table super_users in database with fields :user_id, :name:
class SuperUser < ActiveRecord::Base
belongs_to :user
end
I would like the SuperUser to be an extension of User so as to be able to do this :
SuperUser.create(:name => "foo", :username => "bar", :password => "foobar")
or when I fetch data to get something like this
> s = SuperUser.find 1
> s.username
> "bar"
Has anyone any idea how I can do this?
You need to consider two possible patterns of extending your models. One of them, Single Table Inheritance is built into Rails and is well documented. The other pattern, the Multi Table Inheritance, which is surprisingly less welcomed in Rails. You need to implement it yourself or use less popular gem for it.
One of the reasons why, I think, MTI is not well supported in Rails is related to performance. For example, even simple User.find 1 should be a lookup in two tables.
What you ask in your question (having two tables) is MTI. But why not STI in this simple case? Are User and SuperUser as deviated from each other that you worry about saving space and using two separate tables for them?
For me it looks like going with a single users table, and adding type column to it.
add_column :users, :type, :string
Now:
class User < ActiveRecord::Base
end
class SuperUser < User
end
and it just works.
You'd want to use either Single Table Inheritance (STI) or Multiple Table Inheritance (MTI). This is not as complicated as it may seem at first. You can read about it here
Related
I have two types of users (regular user, super user). What is the proper way to extend one base user class with additional tables?
I was thinking something like this but I am not sure am I going to right direction:
class User < ActiveRecord::Base
end
class SuperUser < User
end
class RegularUser < User
end
Is this the proper way to do it in Rails? Thanks :)
It is 100% correct approach, however you need to remember, that all your models will be stored in one table in database. This approach is called STI (Single table inheritance) and requires only one additional field type in you model.
If you want to have different types of users I would go with user roles versus different user tables etc.
A very good gem for that is CanCan and the documentation is excellent:
https://github.com/ryanb/cancan/wiki/Role-Based-Authorization
You will also have nice helpers as .can? or .cannot? and more.
Yes, and you should also use single table inheritance. What this means is you should add a column called 'type' to your user model. Rails recognizes the column 'type' and treats it special. Essentially, all entries in your type model will reference another model. In that model, you can define rules for each type. It would also be a good idea to validate your user model so that only the two types you want can be entered. This should work:
class User < ActiveRecord::Base
validates :type, :inclusion => {:in => ['SuperUser', 'RegularUser']}
end
I have tables already created from a different project. Their names are formatted like aaa_bbb_ccc_ddd (all non plural and some parts aren't a convention word). I have successfully created a schema from the database by reading this. But now I have to make the actual models. I've looked at RMRE, but they enforce the ActiveRecord convention on my tables and change their names, which I don't want to do because other apps depend on those tables.
What is the best way to automatically create models and a schema from existing tables?
just a theory, not sure how this would work in real app:
create models named as ActiveRecord convention requires, for example for table aaa_bbb_ccc_ddd you'll create a model AaaBbb and map this model to your table:
class AaaBbb < ActiveRecord::Base
self.table_name = "aaa_bbb_ccc_ddd"
end
or a more human example:
class AdminUser < ActiveRecord::Base
self.table_name = "my_wonderfull_admin_users"
end
Now you'll have AaaBbb as resource in routes meaning you'll have a url like:
.../aaa_bbb/...
and if you want to use the table name name in url I guess you could rewrite the route:
get 'aaa_bbb_ccc_ddd/:id', "aaa_bbb#show", as: "aaa_bbb"
again, just a theory that might help you out. I haven't worked with such cases yet but would've start from this.
edit
to automate model creation from database:
https://github.com/bosko/rmre
but I think this will create models by rails convention with wierd names that you'll have to use as resource in your app.
A good template that I found on SO in case you want to use a model name different from table name:
class YourIdealModelName < ActiveRecord::Base
self.table_name = 'actual_table_name'
self.primary_key = 'ID'
belongs_to :other_ideal_model,
:foreign_key => 'foreign_key_on_other_table'
has_many :some_other_ideal_models,
:foreign_key => 'foreign_key_on_this_table',
:primary_key => 'primary_key_on_other_table'
end
Just switch from Rails to Django and make
your life happier and also make your work normal:
$ python manage.py inspectdb my_table_without_existing_model > some_new_model.py
That's enough. Two seconds of work :)
I'm new to Rails, still getting my feet wet, so please pardon me if this is either trivial or "the wrong way" to do things.
I'd like to create a superclass for some scaffolded models. For example, I'd like to create a scaffold for Men and for Women, but I want them both to inherit from a People superclass; Men and Women would inherit fields like height and weight from the People class.
Where/how do I define this People superclass? How do I define the subclasses Men and Women via scaffolding?
Usually I do something like:
rails g scaffold People type:string name:string birth:date height:integer
class People < ActiveRecord::Base
end
Important use the reserved word 'type'! That's where the table will keep which type the class is. Run the migration.
So, for the the subclasses you can do:
rails g scaffold Men --parent=People
resulting Men:
class Men < People
end
Same for Women:
rails g scaffold Women --parent=People
Resulting
class Women < People
end
No migration will be generated for the subclasses.
I'm not sure but this approach only works for STI.
Hope it, helps!
This is something I've thought about doing with my application. I haven't done it yet, and I wouldn't recommend it if you are new to rails. I would either make separate models entirely, or make one model, and have the attribute gender, which should be either a 0 or a 1, and then make a method that returns the string for the corresponding gender.
EDIT
So I opened up the rails console, and from what I could see, it is possible totally possible, all you need to do is declare the class, and if you want to use different tables, set_table_name
class This < That
set_table_name :this
end
class There < This
set_table_name :there
end
Or you could use one table, but if your trying to stay DRY, I would use two.
If you want to use the scaffold generator, you will have to run the typical rails g scaffold Men for each class you want views for (men and women). The model that this generates inherits from the ActiveRecord::Base class. The inheritance marker is the less than symbol (<).
# THESE WILL BE THE DEFAULT GENERATED MODELS
class Men < ActiveRecord::Base
end
class Women < ActiveRecord::Base
end
You will then manually create the super class User
class User < ActiveRecord::Base
end
and then edit the Men and Women models to inherit from User
# men.rb
class Men < User
end
# women.rb
class Women < User
end
lets say you wanted to subclass with one table, you could would right the migrations for that table, and then add the attr_accessible to the appropriate subclass.
attr_accessible is a rails security feature. It determines which attributes may be set in mass assignment. Anything related to security, site rank, etc. should not be accessible.
Example:
attr_accessible :favorite_food, :interests, :password, :email # THIS IS GOOD
attr_accessible :admin, :has_access_to_missile_launch_codes # THIS IS BAD
because then someone could undermine your security system by passing
params => { :man => { :admin => true }}
The main point is that using these attr_accessible will determine which type of user can set what. Obviously you can DRY this up by putting shared features in the super-class. Hope this helps
You should also read about the super keyword, and the self keyword. If your running an inherited setup you will eventually want to use these.
AFAIK you'd need to tweak the existing scaffolding templates, I don't believe there's a means to specify the controller base class. That said, I think in Rails 3 you can copy the templates into $ROOT/lib/templates/rails/... where ... depends on which you want to change.
That said, what's the real goal in doing this in a scaffold? In general, models will (a) only rarely be subclasses, and (b) even more rarely be the same subclass.
Just edit them by hand.
watch this screencast on single table inheritance.
http://railscasts.com/episodes/394-sti-and-polymorphic-associations
Single table inheritance and where to use it in Rails
I am working on a very large Rails application. We initially did not use much inheritance, but we have had some eye opening experiences from a consultant and are looking to refactor some of our models.
We have the following pattern a lot in our application:
class Project < ActiveRecord::Base
has_many :graph_settings
end
class GraphType < ActiveRecord::Base
has_many :graph_settings
#graph type specific settings (units, labels, etc) stored in DB and very infrequently updated.
end
class GraphSetting < ActiveRecord::Base
belongs_to :graph_type
belongs_to :project
# Project implementation of graph type specific settings (y_min, y_max) also stored in db.
end
This also results in a ton of conditionals in views, helpers and in the GraphSetting model itself. None of this is good.
A simple refactor where we get rid of GraphType in favor of using a structure more like this:
class Graph < ActiveRecord::Base
belongs_to :project
# Generic methods and settings
end
class SpecificGraph < Graph
# Default methods and settings hard coded
# Project implementation specific details stored in db.
end
Now this makes perfect sense to me, eases testing, removes conditionals, and makes later internationalization easier. However we only have 15 to 30 graphs.
We have a very similar model (to complicated to use as an example) with close to probably 100 different 'types', and could potentially double that. They would all have relationships and methods they inheritated, some would need to override more methods then others. It seems like the perfect use, but that many just seems like a lot.
Is 200 STI classes to many? Is there another pattern we should look at?
Thanks for any wisdom and I will answer any questions.
If the differences are just in the behavior of the class, then I assume it shouldn't be a problem, and this is a good candidate for STI. (Mind you, I've never tried this with so many subclasses.)
But, if your 200 STI classes each have some unique attributes, you would need a lot of extra database columns in the master table which would be NULL, 99.5% of the time. This could be very inefficient.
To create something like "multiple table inheritance", what I've done before with success was to use a little metaprogramming to associate other tables for the details unique to each class:
class SpecificGraph < Graph
include SpecificGraphDetail::MTI
end
class SpecificGraphDetail < ActiveRecord::Base
module MTI
def self.included(base)
base.class_eval do
has_one :specific_graph_detail, :foreign_key => 'graph_id', :dependent => :destroy
delegate :extra_column, :extra_column=, :to => :specific_graph_detail
end
end
end
end
The delegation means you can access the associated detail fields as if they were directly on the model instead of going through the specific_graph_detail association, and for all intents and purposes it "looks" like these are just extra columns.
You have to trade off the situations where you need to join these extra detail tables against just having the extra columns in the master table. That will decide whether to use STI or a solution using associated tables, such as my solution above.
I was hoping I could get feedback on major changes to how a model works in an app that is in production already.
In my case I have a model Record, that has_many PhoneNumbers.
Currently it is a typical has_many belongs_to association with a record having many PhoneNumbers.
Of course, I now have a feature of adding temporary, user generated records and these records will have PhoneNumbers too.
I 'could' just add the user_record_id to the PhoneNumber model, but wouldn't it be better for this to be a polymorphic association?
And if so, if you change how a model associates, how in the heck would I update the production database without breaking everything? >.<
Anyway, just looking for best practices in a situation like this.
Thanks!
There's two approaches that might help you with this.
One is to introduce an intermediate model which handles collections of phone numbers. This way your Record and UserRecord can both belong_to this collection model and from there phone numbers and other contact information can be associated. You end up with a relationship that looks like this:
class Record < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class UserRecord < ActiveRecord::Base
belongs_to :address_book
delegate :phone_numbers, :to => :address_book
end
class AddressBook < ActiveRecord::Base
has_many :phone_numbers
end
This kind of re-working can be done with a migration and a bit of SQL to populate the columns in the address_books table based on what is already present in records.
The alternative is to make UserRecord an STI derived type of Record so you don't need to deal with two different tables when defining the associations.
class Record < ActiveRecord::Base
has_many :phone_numbers
end
class UserRecord < Record
end
Normally all you need to do is introduce a 'type' string column into your schema and you can use STI. If UserRecord entries are supposed to expire after a certain time, it is easy to scope their removal using something like:
UserRecord.destroy_all([ 'created_at<=?', 7.days.ago ])
Using the STI approach you will have to be careful to scope your selects so that you are retrieving only permanent or temporary records depending on what you're intending to do. As UserRecord is derived from Record you will find they get loaded as well during default loads such as:
#records = Record.find(:all)
If this causes a problem, you can always use Record as an abstract base class and make a derived PermanentRecord class to fix this:
class PermanentRecord < Record
end
Update during your migration using something like:
add_column :records, :type, :string
execute "UPDATE records SET type='PermanentRecord'"
Then you can use PermanentRecord in place of Record for all your existing code and it should not retrieve UserRecord entries inadvertently.
Maintenance page is your answer.
Generate migration which updates table structure and updates existing data. If you're against data updates in migrations - use rake task.
Disable web access (create maintenance page)
Deploy new code
Run pending migrations
Update data
Enable web access (remove maintenance page).