So in my rails app, I'm creating a table of users, and in that table I have columns such as "email, password, and image". I want to add a concrete list of nationalities that a user can be associated with.
So, I created a user model:
rails g model User email password image:string
Now I want an array of nationalities... so instead of creating an array, I created a separate model for nationalities and want to associate it to my users. Assuming one can have multiple nationalities, I set it up like this:
rails g model Nationality name:string
So in user.rb I wrote:
has_many :nationalities
and in nationality.rb I wrote:
belongs_to :user
When a user signs up, I will have a list for them to choose from: American, Canadian, French, etc...
My gut tells me this might be an antipattern since a user won't ever be adding new nationalities to the list... only selecting, but an admin can always add more nationalities.
I'm not getting any errors, but it isn't working in my favor either.
Did I set it up correctly, or should I just use an array and store it in the user's table under a column called "nationalities"?
edit: Oops, I didn't address the part of having multiple nationalities. You may want to consider using acts_as_taggable_on
https://github.com/mbleigh/acts-as-taggable-on
It nicely handles the many-to-many behavior of tagging with some built in conveniences.
I think it's fine to use some kind of global array (in a namespace) for Nationalities for now, because as you note correctly, the kind of Nationalities will not change.
However, I think things will be a little more complicated than a straight-up Array...You say you will give choices such as American, Canadian, French...and that's fine, but what if the context of a view calls for using U.S., Canada, France instead? Then your Array becomes a Hash...I doubt it'll get complicated enough to require a full fledged Rails model. A simple Hash should cover most of those use cases.
I've found it helpful in such situations to create a separate Class or Module. For example, with Gender, it's most definitely not worth creating a separate DB table...and yet, even if all I store in a user's gender column is "M", "F", "O" (for other, or maybe not specified), I'll create a Gender module that can handle that domain logic:
class Gender
# better to make this private so that
# only the factory constructor is exposed
def initialize(g)
#g = g
end
def to_s # and equivalent methods for serializing Gender into a M/F for database
return #g.to_s
end
def pronoun
case #g
when :M
'he'
when :F
'she'
else
'they'
end
end
def Gender(str)
case str
when /^(?:m|male|man|boy)$/i
Gender.new(:M)
when /^(?:f|female|woman|girl)$/i
Gender.new(:F)
else
Gender.new(:O)
end
end
This is really more a database design question than a Rails question, but I would just have the Nationality table serve as the source for a dropdown, but the User table would simply have a nationality column. I don't see the need for the extra join. Of course, the values in the nationality column would come from user selections from that dropdown populated by the Nationality table.
Still, your needs may vary. Stick with the join if multiple nationalities are a requirement. As Knuth said, "Premature optimization is the root of all evil."
Are you using Rails 4? Is Postgresql your DB? If so this combination allows you to store arrays on your user model.
Basically you would modify your migration to look like this:
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name # or whatever attributes you've already defined
t.string :nationalities, array: true, default: []
t.timestamps
end
end
end
Your ActiveRecord model would contain this:
class User < ActiveRecord::Base
attr_accessible :nationalities
end
This would allow you to create the nationalities list like so:
user = User.create(name: 'Bob', nationalities: ['English', 'Australian', 'French'])
And obviously extract them like:
user.nationalities
=> ['English', 'Australian', 'French']
Still, the decision about having an association or a column on the user goes beyond the ease of creating the user with a nationality in a form. The real question is "what role will the nationality play in the app"? Meaning, does the nationality have additional data that would warrant it being it's own model? Or, can it all just sit in the user table?
If the nationality has as much functionality as a user name, than it can stay on the user. However, if nationality can have other attributes on it such as 'date_aquired' than it may have to be it's own model.
Related
When using model and migration generators in rails, you can use 'references' keyword as it was a data type to indicate that that field is a foreign key to another model, that is another table.
In this example I do so with an author field inside a book table (actually the field won't be exactly 'author').
rails g model book title publication_date:date author:references
Looking at the migrations and models created, we can better understand what are the information that rails has guessed from the command (ok this is not a part of the question but more a summary of the logic and the state of the art of this command):
Migration
class CreateBooks < ActiveRecord::Migration
def change
create_table :books do |t|
t.string :title
t.references :author, index: true
t.date :publication_date
t.timestamps
end
end
end
Here rails uses again the 'references' method as it was a data type, but it actually means that we are a layer on top on the 'basic' data structure level, infact referencing an author means, at the data level, to add an 'author_id' and not an 'author' column . And it does right so (i've also checked on schema.rb to have confirmation).
In addition to this, it also puts an index: true that is a good practice from a performances point of view.
Model
Calling the generator in such a way also 'does something' on the model:
class Book < ActiveRecord::Base
belongs_to :author
end
that is, it adds the 'belongs_to :author'.
Notice that you can even create the book model and migration with the references clause to author before having author created. It enforces the consistence at the end (if it does at all, on such things).
Question
My question is on what rails doesn't do, that is: the has_many clause on the author model.
1) since normally it does exist before having created the book model, it should be easy for rails to add has_many to this. Is there any parameter to say rails to do so?
2) because of the fact that models can reference other models even if they are not yet created, another possibility would be declaring some kind of 'has_many' reference during author creation, is it possibile in some way?
There's no way of knowing where to put it, you'd have to specify it.
Such generators are designed to save time, and making such a thing saves too little or none. For instance, you'll have to specify explicitly the target class and whether it is has_one or has_many. That would subtract some of the usefulness of this, resulting in a very small positive or even a negative value.
I assume you mean that associations are available even on unsaved models. Yep, they are, but they're not too reliable. It works quite simple: an unsaved object has a collection for each association which stores temporary objects. When the main object is saved, new associated objects in these collections (not persisted yet) are created. An example:
a = Author.new(name: "Charles Dickens")
a.books << Book.new(title: "Oliver Twist")
a.save
That would save the new author to the database, and since his id would be known, it would then create a new book with that author_id. This behaves a little odd when it comes to has_many ... through: ... and possibly some other cases, so use this carefully.
Every time I create a new company record in rails, I need to add some default (blank) contact records at that company. Front Desk, Receiving, HR, IT and so on...they won't have any data in them besides the name, just a placeholder for the user to fill in later.
So, my company model has_many contacts, and contacts belong_to company. The contact records are static and the same for every new company that gets added, but I need to pre-populate the contacts table with data, so my users don't have to.
I've read a lot about seeding the database, but I won't be able to use the terminal every time a user dynamically creates a company, and it needs to be dynamically tied to that company, the records are not agnostic. Seeding doesn't seem to be the right thing. How should this be done?
you should use a before_save filter, which checks if an attribute is empty, and otherwise set it to the default.
Using a before_save will guard against deletions later on.
But be careful only to do this for fields which will never be empty.
class Company < ActiveRecord::Base
has_many :contacts
before_save :add_defaults
def add_defaults
contacts ||= Contact.default_list # this only sets it if it's nil
# you can implement Contact#default_list as a method, or as a scope in the contacts model
end
end
What about after_create callback in Company Model?
Smth like this:
class Company < ActiveRecord::Base
has_many :contacts
after_create :add_contacts
def add_contacts
contacts.create(name: "Some name", phone: "...", ....)
end
end
Although it notionally exists for generating test data, the FactoryGirl gem is very useful for this purpose. Use it in conjunction with the after_save approach mentioned here, and you'll have a nice place to centrally define your blank records.
I'm a bit confused about STI in rails.
My situation:
I have a Contact model that has description and data string fields, to store some contact like phone, fax, email, etc.
Now when I have some specific contact type like phone number of email address I want to walidate the data format in different way and I want to make some different formating on output.
I decided to use STI as all the models have the same data with just different behaviour. And I have some questions regarding forms and CRUD operations as I don't want to go against Rails conventions.
How do I make a dropdown list in form with model type? Should I hardcode it or is there some more automated way?
How do I create a record? Should I use switch statement and according to received type create new model of according instance?
How should I update it if I'm going to change the model type? Cast the object to new class? Or create a new object and destroy the previous one?
I'll be very thankfull for your help!
Yes, should do a hardcore as there no default store for your STI models.
Generally, yes. But With Rails you could just use camelize.constantize to get class from string. Another way is just use parent model, and set type field manually. As with STI all records are in the same table and then all are of the parent class.
If you wish to update, just update type field. Then you could re-query to force Rails to get new object of different type.
You could create a model like this :
Type < ActiveRecord::Base
has_many :contacts
end
You could use this command rails g model Type name:string, add a type_id column in your contact and migrate the database.
end change your contact's model like this :
Contact < ActiveRecord::Base
belongs_to :type
end
Now, in your form, you could use this :
select("type", "type_id", Type.all.collect {|t| [ t.name, t.id ] }, { :include_blank => true })
It should resolve your problem.
Now you can do something like this :
#emails = Type.find_by_name('email').contacts
Or use scopes.
Please help a newbie to choose the best way to implement inheritance in RoR3. I have:
-Person (address fields, birthdate, etc.)
-Player, inherits from Person (position, shoe_size, etc.)
-Goalkeeper, inherits from Player (other specific fields related to this role)
I think that Single Table Inheritance is a bad solution, because there will be a lot of null fields in the table created. What is the best way to do this? Use polymorphic associations (with has_one?)? Use belongs_to/has_one (but then how to show in the Player views the fields of Person too?)? Don't implement inheritance? Other solutions?
While I think STI is probably the approach I would use for this, one other possibility, if you want to avoid a lot of NULL attributes, is to add a column other_attributes to your Person model that will store a Hash of attributes. To do this, add a text column to the people table:
def self.up
add_column :people, :other_attributes, :text
end
Then make sure the attribute is serialized in the model. And you may want to write a wrapper to make sure it's initialized as an empty Hash when you use it:
class Person < ActiveRecord::Base
serialize :other_attributes
...
def other_attributes
write_attribute(:other_attributes, {}) unless read_attribute(:other_attributes)
read_attribute(:other_attributes)
end
end
Then you can use the attribute as follows:
p = Person.new(...)
p.other_attributes #=> {}
pl = Player.new(...)
pl.other_attributes["position"] = "forward"
pl.other_attributes #=> {"position" => "forward"}
One caveat with this approach is that you should use strings as keys when retrieving data from other_attributes, as the keys will always be strings when the Hash is retrieved from the database.
I suggest STI. An alternative solution is to use a document store like mongodb, or use the activerecord store http://api.rubyonrails.org/classes/ActiveRecord/Store.html. If you have a postgress database look at his HStore column http://rubygems.org/gems/activerecord-postgres-hstore.
Another option is PostgreSQL table inheritance. http://www.postgresql.org/docs/8.1/static/ddl-inherit.html
I'm developing a website with Ruby on Rails.
I want to find the better way to let users (not developers) to edit text on some pages (like the index...). (like a CMS ?)
Actually they had to get the page through FTP, to edit the text and to put the new file on the server (through FTP).
It's a very very bad practice and I wanted to know if someone has an idea to solve this problem ?
Many thanks
It would be the same as the basic Rails CRUD operations. Just make a model/controller representing page content, and an edit view for the controller. Then on the pages you want text to be editable, instead of having the content directly on the page just use a view partial.
Of course, you would probably also want to implement some type of authentication to make sure not just everyone can edit pages.
Well, one thing you could do is add a model to your database called "Content" or "Copy" which represents some text on a page. Then you could use polymorphic association to link the content/copy to your actual model. For instance, if you had a page with a list of products on it, you'd likely have a Product model in your database. You could do something like this:
class Content < ActiveRecord::Base
belongs_to :contentable, :polymorphic => true # excuse my lame naming here
# this model would need two fields to make it polymorphic:
# contentable_id <-- Integer representing the record that owns it
# contentable_type <-- String representing the kind of model (Class) that owns it
# An example would look like:
# contentable_id: 4 <--- Product ID 4 in your products table
# contentable_type: Product <--- Tells the rails app which model owns this record
# You'd also want a text field in this model where you store the page text that your
# users enter.
end
class Product < ActiveRecord::Base
has_many :contents, :as => :contentable # again forgive my naming
end
In this scenario, when the product page renders, you could call #product.contents to retrieve all the text users have entered for this product. If you don't want to use two separate models like this, you could put a text field directly on the Product model itself and have users enter text there.