I'm trying to create default seed records for every user that signs up to the app. I'm thinking I could use the after_create method in my users observer model:
def after_create(user)
user.recipes.create(:name => "Sample Recipe", :description => "This is a sample recipe.")
user.cuisines.create(:name => "Sample Cusine", :description => "This is a sample cuisine.")
...
end
Is that too resource-intensive if I have 10 models that need seed data upon signup? Is there a more efficient way?
You're doing this the correct way, and here's why:
As business logic (every user should start with a sample cuisine and recipe) it belongs in the model.
This is where it is most easily testable.
If they have to be created for each user anyway, there's no less "resource intensive" way to do it. Any kind of batch process would leave the user without these defaults for a time.
Personally, I'd probably skip the added abstraction and complexity of putting it in the observer, because I'd want it obvious upon reading through the model that this is happening. But that's personal preference, and there's nothing wrong with how you've set it up here.
Why not set those as the default values in your database? That way there's no extra resources being used code-wise, it is one less point of failure, and you don't need to manually build up the samples in the associations. You can give a default to a column like this:
class AddRecipesDefaults < ActiveRecord::Migration
def self.up
change_column :recipes, :name, :string, :default => "Sample Recipe"
change_column :recipes, :default, :string, :default => "This is a sample."
end
def self.down
change_column :recipes, :name, :string, :default => nil
change_column :recipes, :name, :string, :default => nil
end
end
Related
I'm trying to figure out the best way to build my model. Each user can have many balances, but I would like to enforce one balance of each currency per user. The application controls the record generation, so perhaps this is overkill. However, the question perplexed me, so I thought I'd ask the community.
If it makes sense to do, what would be the best way to build this?
My migration thus far:
class CreateBalances < ActiveRecord::Migration
def change
create_table :balances do |t|
t.decimal :amount
t.integer :currency, default: 0, null: false # this will be an enum in the model
t.references :user, index: true
t.timestamps
end
end
end
TL;DR: one of each :currency per :user
Try this in your Balance model:
validates :currency, :uniqueness => true, :scope => user_id
What I think this says (and I could be wrong, so please take this with a grain of salt) is, "Make sure that this type of currency exists only once for this user." Failing that, you could also try a custom validation:
validates :unique_currency_balance_per_user
def unique_currency_balance_per_user
Balance.where('id != ?', id)
.where(:currency => currency, :user_id => user_id)
.present?
end
There are also likely to be database-level constraints, but I am not yet aware of these.
I have a Users model and a Questions model. Each user has_one Question and each questions belongs_to a User. The Questions model has 3 columns - QuestionOne, QuestionTwo and QuestionThree - each is set to a default value of the string "TBD".
When the user creates an account and signs in I want to display on his profile the Questions and the responses = "TBD" and then using Best in place I want him to be able to edit his responses using the edit and update actions. But I cant retrieve the questions using user.question.questionone because they are sit to nil as there is no create or new action here. How do I go about this?
# user.rb
class User << ActiveRecord::Base
has_one :question
after_create :create_question
def create_question
question = self.build_question
question.question_one = question.question_two = question.question_three = "TBD"
question.save
end
end
When new user is created, it does not have associated question yet, so you need to build it somehow. Common way is using ActiveRecord callbacks before_create or after_create, something like
# user.rb
class User << ActiveRecord::Base
has_one :question
after_create :create_question
end
Here you go, you can write a migration that add default questions whenever the question is created,
class ChangeDefaultQuestions < ActiveRecord::Migration
def self.up
change_column :questions, :question_one, :string, :default => 'What is your name?'
change_column :questions, :question_two, :string, :default => 'How old are you?'
change_column :questions, :question_three, :string, :default => 'Where do you live'
# to add default questions to previously created questions
Questions.update_all({ :question_one => 'your qestion', :question_two => 'your question', :question_three => 'your question' })
end
def self.down
change_column :movies, :rating, :string, :default => nil
Questions.update_all({ :question_one => '', :question_two => '', :question_three => '' })
end
end
i am sure it would answer your question
I am new to the concept of counter caching and with some astronomical load times on one of my app's main pages, I believe I need to get going on it.
Most of the counter caches I need to implement have certain (simple) conditions attached. For example, here is a common query:
#projects = employee.projects.where("complete = ?", true).count
I am stumbling into the N+1 query problem with the above when I display a form that lists the project counts for every employee the company has.
Approach
I don't really know what I'm doing so please correct me!
# new migration
add_column :employees, :projects_count, :integer, :default => 0, :null => false
# employee.rb
has_many :projects
# project.rb
belongs_to :employee, :counter_cache => true
After migrating... is that all I need to do?
How can I work in the conditions I mentioned so as to minimize load times?
With regards to the conditions with counter_cache, I would read this blog post.
The one thing you should do is add the following to the migration file:
add_column :employees, :projects_count, :integer, :default => 0, :null => false
Employee.reset_column_information
Employee.all.each do |e|
Employee.update_counters e.id, :projects_count => e.projects.length
end
So you current projects count can get migrated to the new projects_count that are associated with each Employee object. After that, you should be good to go.
Check counter_culture gem:
counter_culture :category, column_name: Proc.new {|project| project.complete? ? 'complete_count' : nil }
You should not use "counter_cache" but rather a custom column :
rails g migration AddCompletedProjectsCountToEmployees completed_projects_count:integer
(add , :default => 0 to the add_column line if you want)
rake db:migrate
then use callbacks
class Project < ActiveRecord::Base
belongs_to :employee
after_save :refresh_employee_completed_projects_count
after_destroy :refresh_employee_completed_projects_count
def refresh_employee_completed_projects_count
employee.refresh_completed_projects_count
end
end
class Employee
has_many :projects
def refresh_completed_projects_count
update(completed_projects_count:projects.where(completed:true).size)
end
end
After adding the column, you should initialize in the console or in the migration file (in def up) :
Employee.all.each &:refresh_completed_projects_count
Then in your code, you should call employee.completed_projects_count in order to access it
Instead of update_counters i use update_all
You don't need the Employee.reset_column_information line AND it's faster because you are doing a single database call
Employee.update_all("projects_count = (
SELECT COUNT(projects.id) FROM projects
WHERE projects.employee_id = employees.id AND projects.complete = 't')")
I am new to the concept of counter caching and with some astronomical load times on one of my app's main pages, I believe I need to get going on it.
Most of the counter caches I need to implement have certain (simple) conditions attached. For example, here is a common query:
#projects = employee.projects.where("complete = ?", true).count
I am stumbling into the N+1 query problem with the above when I display a form that lists the project counts for every employee the company has.
Approach
I don't really know what I'm doing so please correct me!
# new migration
add_column :employees, :projects_count, :integer, :default => 0, :null => false
# employee.rb
has_many :projects
# project.rb
belongs_to :employee, :counter_cache => true
After migrating... is that all I need to do?
How can I work in the conditions I mentioned so as to minimize load times?
With regards to the conditions with counter_cache, I would read this blog post.
The one thing you should do is add the following to the migration file:
add_column :employees, :projects_count, :integer, :default => 0, :null => false
Employee.reset_column_information
Employee.all.each do |e|
Employee.update_counters e.id, :projects_count => e.projects.length
end
So you current projects count can get migrated to the new projects_count that are associated with each Employee object. After that, you should be good to go.
Check counter_culture gem:
counter_culture :category, column_name: Proc.new {|project| project.complete? ? 'complete_count' : nil }
You should not use "counter_cache" but rather a custom column :
rails g migration AddCompletedProjectsCountToEmployees completed_projects_count:integer
(add , :default => 0 to the add_column line if you want)
rake db:migrate
then use callbacks
class Project < ActiveRecord::Base
belongs_to :employee
after_save :refresh_employee_completed_projects_count
after_destroy :refresh_employee_completed_projects_count
def refresh_employee_completed_projects_count
employee.refresh_completed_projects_count
end
end
class Employee
has_many :projects
def refresh_completed_projects_count
update(completed_projects_count:projects.where(completed:true).size)
end
end
After adding the column, you should initialize in the console or in the migration file (in def up) :
Employee.all.each &:refresh_completed_projects_count
Then in your code, you should call employee.completed_projects_count in order to access it
Instead of update_counters i use update_all
You don't need the Employee.reset_column_information line AND it's faster because you are doing a single database call
Employee.update_all("projects_count = (
SELECT COUNT(projects.id) FROM projects
WHERE projects.employee_id = employees.id AND projects.complete = 't')")
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