how (replace|create) an enum field on rails 2.0 migrations? - ruby-on-rails

I would like to create an enum field at sone migration I'm doing, I tried searching in google but I can't find the way to do it in the migration
the only thing I found was
t.column :status, :enum, :limit => [:accepted, :cancelled, :pending]
but looks like the above code runs only on rails 1.xxx and since I'm running rails 2.0
this what I tried but it fails
class CreatePayments < ActiveRecord::Migration
def self.up
create_table :payments do |t|
t.string :concept
t.integer :user_id
t.text :notes
t.enum :status, :limit => [:accepted, :cancelled, :pending]
t.timestamps
end
end
def self.down
drop_table :payments
end
end
So, in case that isn't allowed, what do you think could be a good solution? just a text field, and validating from the model?

You can manually specify the type by using the t.column method instead. Rails will interpret this as a string column, and you can simply add a validator to the model like Pavel suggested:
class CreatePayments < ActiveRecord::Migration
def self.up
create_table :payments do |t|
t.string :concept
t.integer :user_id
t.text :notes
t.column :status, "ENUM('accepted', 'cancelled', 'pending')"
t.timestamps
end
end
def self.down
drop_table :payments
end
end
class Payment < ActiveRecord::Base
validates_inclusion_of :status, :in => %w(accepted cancelled pending)
end

Look at tip #3 on http://zargony.com/2008/04/28/five-tips-for-developing-rails-applications
This exactly what you need!
class User < ActiveRecord::Base
validates_inclusion_of :status, :in => [:active, :inactive]
def status
read_attribute(:status).to_sym
end
def status= (value)
write_attribute(:status, value.to_s)
end
end
HTH

You can try the (very) comprehensive jeff's enumerated_attribute gem OR go with this simple workaround:
class Person < ActiveRecord::Base
SEX = [:male, :female]
def sex
SEX[read_attribute(:sex)]
end
def sex=(value)
write_attribute(:sex, SEX.index(value))
end
end
And then declare the sex attribute as an integer:
t.integer :sex
This worked very fine for me! =D

I have dozens of these little enums, with 3-300 entries in each. I implement them as lookup tables. I don't have a model file for each one; I use some metaprogramming to generate a model for each, since each table has the same set of columns (id, name, description).
Since some of the sets had enough elements to warrant their own table, it was more consistent to move them all to tables. Just another option if you'll have more of these enums later.
EDIT: Here's how I generate the models:
ACTIVE_RECORD_ENUMS = %w{
AccountState
ClientType
Country
# ...
}
ACTIVE_RECORD_ENUMS.each do |klass|
eval "class #{klass} < ActiveRecord::Base; end"
klass.constantize.class_eval do
class << self
def id_for(name)
ids[name.to_s.strip.humanize.downcase]
end
def value_for(id)
values[id.to_i]
end
def values
#values ||= find(:all).inject({}) {|h,m| h[m.send(primary_key)] = m.name; h}
end
def ids
#ids ||= self.values.inject({}) {|h, {k, v}| h[v.downcase] = k; h}
end
end
end
end
This file lives in the models directory, and is included in application_config.rb. This lets me do stuff like this:
AccountState.ids
# => {"active" => 1, "deleted" => 2}
AccountState.values
# => {1 => "Active", 2 => "Deleted"}
AccountState.id_for("Active")
# => 1
AccountState.value_for(1)
# => "active"

Have you looked at the enum-column plugin on RubyForge?

Similarly, the enumerated_attribute gem manages enums at the object level.
enum_attr :status, %w(accepted cancelled ^pending)
Define a string in the migration
t.string :status
Also provides some nice features like dynamic predicate methods.
http://github.com/jeffp/enumerated_attribute/tree/master

Add the following:
module ActiveRecord
module ConnectionAdapters #:nodoc:
class TableDefinition
def enum(*args)
options = args.extract_options!
column_names = args
column_names.each { |name| column(name, 'enum', options) }
end
end
end
end
to lib/enum/table_definition.rb and include it in your init.rb.

ok, just read the whole rails api and found what I neeed and I dont like :(
Rails doesn't support emum as native type on migrations, here is the info, I need to search for a plugin or other method.
I will keep you posted.

Another option: drop to SQL.
def self.up
execute "ALTER TABLE `payments` ADD `status` ENUM('accepted', 'cancelled', 'pending')"
end

With simple_form i use this:
<%= f.input :gender, :collection => {'Male' => 'male','Female' => 'female'}, :include_blank => false %>

Related

Translations for each model

In my domain, many models have names, descriptions, etc. These properties need translations. I know how to represent this in a database. However I struggle finding a way to represent this with Rails.
|-------translations-table--------|
|translation_id|locale|translation|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|----------------------modelx-table---------------------|
|id|name_translation_id|description_translation_id|price|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
|-------modely-table--------|
|id|name_translation_id|date|
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
You do not need to create extra models for translations, yo just need to set up locales in .yml format, check this for further instructions
Update
Ok now I understood your point, you want to add translatable fields on your entities/models, so users can manage those translations through a UI right?, well your approach is correct, however there is a gem called Globalize that does the exact same thing but with more toys, and much more standardized as you want.
This is the solution I eventually came up with:
#Models
class Translation
has_many :translation_records
end
class TranslationRecord
(translation_records.find_by :locale => I18n.locale).text
end
class ModelX
belongs_to :name_translation, :class_name => 'Translation'
belongs_to :description_translation, :class_name => 'Translation'
def name
name_translation.current
end
def description
description_translation.current
end
end
#Migrations
class CreateTranslationRecords < ActiveRecord::Migration[5.0]
def change
create_table :translation_records do |t|
t.references :translation
t.string :locale
t.string :text
end
add_index :translation_records, :locale
end
end
class CreateTranslation < ActiveRecord::Migration[5.0]
def change
create_table :translations do |t|
# only id column
end
end
end
class AddTranslationToModelXs < ActiveRecord::Migration[5.0]
def change
add_reference :model_xs, :name_translation
add_reference :model_xs, :description_translation
end
end

Updating Attribute Value in Rails Migration

I have over 100 recipes uploaded through ActiveAdmin (http://activeadmin.info/) in with the following attributes:
class CreateRecipes < ActiveRecord::Migration
def change
create_table :recipes do |t|
t.string :title
t.string :description
t.string :ingredients
t.string :position
t.timestamps
end
end
end
I needed to change position from a string to an integer. I was able to do this with the following:
change_column :table_name, :column_name, :integer
stackoverflow: Rails migration for change column
The issue is that I do not know how to go back and reassign all the recipes with a position (now that it's an integer). I basically want to start at 0 and go all the way to 100. And if i create a new recipe, it automatically has the position value of 101.
Is there a way to do this without going back and individually changing each recipe?
It sounds like you want to set :position to :id initially. You could do that through the rails console like so:
recipes = CreateRecipes.all
recipes.each do |recipe|
recipe.position = recipe.id
end
Then, for new recipes, in your model (create_recipes.rb), you could add:
after_initialize :default_values
...
def default_values
self.position ||= id
end
Incidentally, this is a nice clean way to handle default or initial values in general. For more information, see this excellent post How can I set default values in ActiveRecord?.
You can have the conversion run automatically as part of the migration itself. Add the code to convert the values in the existing records into the migration. Use self.up and self.down to have the appropriate conversion code for that direction of the migration:
class ChangeRecipePositionToInteger < ActiveRecord::Migration
def self.up
position_values = Hash[ Recipe.all.map{|r| [r.id, r.position]}]
change_column :recipes, :position, :integer
position_values.each_pair do |id, position_value|
recipe = Recipe.find( id )
recipe.position = position_value.to_i
recipe.save
end
end
def self.down
position_values = Hash[ Recipe.all.map{|r| [r.id, r.position]}]
change_column :recipes, :position, :string
position_values.each_pari do |id, position_value|
recipe = Recipe.find( id )
recipe.position = position_value.to_s
recipe.save
end
end
end

Dynamically creating models for a table in rails

I have a migration that will dynamically create tables on fly per date. Something like this:
class CreateCollectorPeriodTable < ActiveRecord::Migration
def self.create_with(name)
create_table name.to_sym do |t|
t.string :text, :limit => 1024
end
end
end
I want to create a model that will access this migration..
I did read this: Rails Generate Model from Existing Table?, but in another question someone explained why I shouldn't try and make one model fit many tables..
Any suggestions?
class CreateCollectorPeriodTable < ActiveRecord::Migration
# name should be plural
# i.e.: name = 'chickens'
def self.create_with(name)
create_table name.to_sym do |t|
t.string :text, :limit => 1024
end
model_file = File.join("app", "models", name.singularize+".rb")
model_name = name.singularize.capitalize
File.open(model_file, "w+") do |f|
f << "class #{model_name} < ActiveRecord::Base\nend"
end
end
end

How do I add some inserts in rails migration?

After creating a table (by migration), I want to insert some entries directly. How must I write a migration for this?
thanks
Don't. If you're looking for seed data, you should use db/seeds.rb and rake db:seed instead. More info in this Railscast.
Side note: Always make sure that the code in db/seeds.rb is idempotent. i.e. It should always be safe to re-run your seeds.
But, if you must insert or modify data inside a migration (there are legitimate use-cases for this), it's best to use SQL statements instead. Your model class isn't guaranteed to still be around in the same form in a future version of your application, and running the migrations from scratch in the future might yield errors if you reference the model class directly.
execute "insert into system_settings (name, label, value) values ('notice', 'Use notice?', 1)"
Update:
This is the right answer: https://stackoverflow.com/a/2667747/7852
Here's an example from ruby on rails api:
class AddSystemSettings < ActiveRecord::Migration
# create the table
def self.up
create_table :system_settings do |t|
t.string :name
t.string :label
t.text :value
t.string :type
t.integer :position
end
# populate the table
SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
end
def self.down
drop_table :system_settings
end
end
Edit: PLEASE NOTE - Posters above are correct, you should not populate the DB inside migrations. Don't use this to add new data, only to modify data as part of changing the schema.
For many things, using raw SQL will be preferable, but if you need to insert data as part of a migration (for instance, doing data conversion when breaking out a table into multiple tables), and you want some default AR stuff like convenient DB-independent escaping, you can define a local version of the model class:
class MyMigrationSucksALittle < ActiveRecord::Migration
class MyModel < ActiveRecord::Base
# empty guard class, guaranteed to have basic AR behavior
end
### My Migration Stuff Here
### ...
end
Note that this works best for simple cases; since the new class is in a different namespace (MyMigrationSucksALittle::MyModel), polymorphic associations declared in the guard model won't work correctly.
A somewhat more detailed overview of available options is located here: http://railsguides.net/2014/01/30/change-data-in-migrations-like-a-boss/
create a new migration file like
047_add_rows_in_system_settings.rb
class AddRowsInAddSystemSettings < ActiveRecord::Migration
def self.up
SystemSetting.create{:name => "name1", :label => "Use notice?", :value => 1}
SystemSetting.create{:name => "name2", :label => "Use notice?", :value => 2}
end
def self.down
SystemSetting.delete_all
end
end
OR
while creating table
046_system_settings.rb
class AddSystemSettings < ActiveRecord::Migration
def self.up
create_table :system_settings do |t|
t.string :name
t.string :label
t.text :value
t.string :type
t.integer :position
end
SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
end
def self.down
drop_table :system_settings
end
end
Ref:- http://api.rubyonrails.org/classes/ActiveRecord/Migration.html

Activerecord association question: getting has_many :through to work

I'm building an app in Ruby on Rails, and I'm including 3 of my models (and their migration scripts) to show what I'm trying to do, and what isn't working. Here's the rundown: I have users in my application that belong to teams, and each team can have multiple coaches. I want to be able to pull a list of the coaches that are applicable to a user.
For instance, User A could belong to teams T1 and T2. Teams T1 and T2 could have four different coaches each, and one coach in common. I'd like to be able to pull the list of coaches by simply saying:
u = User.find(1)
coaches = u.coaches
Here are my migration scripts, and the associations in my models. Am I doing something incorrectly in my design? Are my associations correct?
class CreateUsers < ActiveRecord::Migration
def self.up
create_table :users do |t|
t.column :login, :string, :default => nil
t.column :firstname, :string, :default => nil
t.column :lastname, :string, :default => nil
t.column :password, :string, :default => nil
t.column :security_token, :string, :default => nil
t.column :token_expires, :datetime, :default => nil
t.column :legacy_password, :string, :default => nil
end
end
def self.down
drop_table :users
end
end
class CreateTeams < ActiveRecord::Migration
def self.up
create_table :teams do |t|
t.column :name, :string
end
end
def self.down
drop_table :teams
end
end
class TeamsUsers < ActiveRecord::Migration
def self.up
create_table :teams_users, :id => false do |t|
t.column :team_id, :integer
t.column :user_id, :integer
t.column :joined_date, :datetime
end
end
def self.down
drop_table :teams_users
end
end
Here are the models (not the entire file):
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
has_many :coaches, :through => :teams
class Team < ActiveRecord::Base
has_many :coaches
has_and_belongs_to_many :users
class Coach < ActiveRecord::Base
belongs_to :teams
end
This is what happens when I try to pull the coaches:
u = User.find(1)
=> #<User id: 1, firstname: "Dan", lastname: "Wolchonok">
>> u.coaches
ActiveRecord::StatementInvalid: Mysql::Error: #42S22Unknown column 'teams.user_id' in 'where clause': SELECT `coaches`.* FROM `coaches` INNER JOIN teams ON coaches.team_id = teams.id WHERE ((`teams`.user_id = 1))
Here's the error in sql:
Mysql::Error: #42S22Unknown column 'teams.user_id' in 'where clause': SELECT coaches.* FROM coaches INNER JOIN teams ON coaches.team_id = teams.id WHERE ((teams.user_id = 1))
Am I missing something in my :through clause? Is my design totally off? Can someone point me in the right direction?
You can't do a has_many :through twice in a row. It'll tell you that its an invalid association. If you don't want to add finder_sql like above, you can add a method that mimics what you're trying to do.
def coaches
self.teams.collect do |team|
team.coaches
end.flatten.uniq
end
It's more of a many-to-many-to-even-more-relationship. I'd just write some sql:
has_many :coaches, :finder_sql => 'SELECT * from coaches, teams_users WHERE
coaches.team_id=teams_users.team_id
AND teams_users.user_id=#{id}'
I don't think ActiveRecord can handle doing a 2 step join in a has_many relationship. In order for this to work you'll have to join users to team_users to teams to coaches. The through option only allows for one extra join.
Instead you'll have to use the :finder_sql option and write out the full join clause yourself. Not the prettiest thing in the world, but that's how it goes with ActiveRecord when you try to do something out of the ordinary.
You could drop the "has_many :coaches, :through => :teams" line in users & then hand-write a coaches method in your User model like so:
def coaches
ret = []
teams.each do |t|
t.coaches.each do |c|
ret << c
end
end
ret.uniq
end
While I love to write SQL, I don't think it's the ideal solution in this instance. Here's what I ended up doing in the User model:
def coaches
self.teams.collect do |team|
team.coaches
end.flatten.uniq
end
def canCoach(coachee)
u = User.find(coachee)
coaches = u.coaches
c = []
coaches.collect do |coach|
c.push(coach.user_id)
end
return c.include?(self.id)
end
I thought about just doing it all in one fell swoop, but I liked the ability to return an array of coach objects from within the user object. If there's a better way to do it, I'm very interested in seeing the improved code.

Resources