I am setting up a fork on one of the projects.
After creating the database I:
rake db:schema:load
then
rake db:seed
to load seed data.
This throws an error
rake aborted! Validation failed: Webiso account has already been
taken, Email has already been taken
Where Webiso and Email are the fields in one of the tables.
I have dropped, created and loaded the schema again.
I have tried the buzzwords. db:reset, db:setup etc.
Nothing works.
Any help would be appreciated.
db/seeds.rb
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
# Mayor.create(:name => 'Daley', :city => cities.first)
require 'factory_girl'
FactoryGirl.define do
factory :xx, :parent => :person do
first_name "xx"
last_name "xx"
twiki_name "xx"
human_name "xx"
email "xxx#xx"
is_staff 1
end
factory :ed, :parent => :person do
first_name "Ed"
last_name "xx"
twiki_name "xx"
human_name "xx"
email "xx#xx"
is_staff 1
image_uri "/images/staff/xx.jpg"
end
factory :anubhav, :parent => :person do
is_student 1
is_part_time 0
graduation_year "2021"
masters_program "SE"
masters_track "Tech"
twiki_name "AnubhavAeron"
first_name "Anubhav"
last_name "Aeron"
human_name "Anubhav Aeron"
email "xx#xx"
webiso_account "x#xx"
end
end
Factory(:task_type, :name => "Working on deliverables")
Factory(:task_type, :name => "Readings")
Factory(:task_type, :name => "Meetings")
Factory(:task_type, :name => "Other")
xx = Factory.create(:xx)
xx = Factory.create(:xx)
Factory.create(:anubhav)
Factory.create(:team_terrific) #This will create awe_smith, betty_ross, and charlie_moss
FactoryGirl.create(:presentation_feedback_questions, :label => "Content", :text => "Did the talk cover all the content suggested on the checklist? (ie goals, progress, and the process for achieving the goals, outcomes)")
FactoryGirl.create(:presentation_feedback_questions, :label => "Organization", :text => "How logical was the organization? How smooth were transactions between points and parts of the talk? Was the talk focused? To the point? Were the main points clearly stated? easy to find?")
FactoryGirl.create(:presentation_feedback_questions, :label => "Visuals", :text => "Were they well-designed? Were all of them readable? Were they helpful? Were they manipulated well?")
FactoryGirl.create(:presentation_feedback_questions, :label => "Delivery", :text => "Bodily delivery: (eye-contact, gestures, energy) Vocal delivery: (loudness, rate, articulation) Question handling (poise, tact, team support; did the team answer the question asked?)")
This (typo?) may be the cause of your error:
xx = Factory.create(:xx)
xx = Factory.create(:xx)
which will try to create two Person instances and save them to the database. Both of them share the same attributes (email) which triggers your validates_uniqueness_of :email (or similar) validation in your Person model.
N.B. if it's not a typo, please have a look into sequences and/or Faker.
Related
I am trying to add parents and their children data in the parent and child table. I have existing data in these tables and I am trying to add further data and I don't want the data to be repeated. Below is the code I am using to upload data. The child has parent_id.
parent.rb
has_many :children, dependent: :destroy
def self.import(file)
CSV.foreach(file.path, headers:true) do |row|
parent = Parent.find_or_update_or_create_by(
parent_1_firstname: row['parent_1_firstname'],
parent_1_lastname: row['parent_1_lastname'],
address: row['address'],
address_line_2: row['address_line_2'],
city: row['city'],
province: row['province'],
postal_code: row['postal_code'],
telephone_number: row['telephone_number'],
email: row['email'],
family_situation: row['admin_notes'],
gross_income: row['gross_income'],
created_by_admin: row['created_by_admin'],
status: row['status']
)
parent.children.find_or_create_by(
firstname: row['firstname'],
lastname: row['lastname'],
dateofbirth: row['dateofbirth'],
gender: row['gender']
)
end
end
child.rb
belongs_to :parent
The error I am facing is when I choose the csv file to be uploaded below is the error which I am getting.
undefined method `find_or_update_or_create_by' for #<Class:0x00007f8797be74b0> Did you mean? find_or_create_by
I have added a sample csv below. Please help me figure out the issue.
parent_1_firstname,parent_1_lastname,address,address_line_2,city,province,postal_code,telephone_number,email,admin_notes,gross_income, created_by_admin ,status,firstname,lastname,dateofbirth,gender
Nav,Deo,College Road,,Alliston,BC,N4c 6u9,500 000 0000,nav#prw.com,"HAPPY",13917, TRUE , Approved ,Sami,Kidane,2009-10-10,Male
undefined method `find_or_update_or_create_by' for
Class:0x00007f8797be74b0 Did you mean? find_or_create_by
AFAIK, there is no find_or_update_or_create_by method in Rails. Unless you have defined it as a class method in the Parent model, you can't call that method on a class. I believe you meant to use find_or_create_by. Change
Parent.find_or_update_or_create_by
to
Parent.find_or_create_by
Update:
You cannot call create unless the parent is saved
Ok, so the parent isn't saved which could be due to any validations has failed. Change Parent.find_or_create_by to Parent.find_or_create_by!(as #jvillian stated) which will raise an exception with the validation error message. Fix the error and you are good to go.
To not have to hard-code various nested loops doing find_or_create_by logic, there is a gem called DutyFree that makes imports and exports like this fairly painless. It intelligently analyses the has_many and belongs_to associations on models and based on these relationships identifies how to properly save each imported row across multiple destination tables. Either a create or an update is performed based on if the data already exists or not.
To demonstrate your example from above, I wrote an RSpec test based on the CSV data you provided:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_complex_spec.rb
There is also a simpler example available with just 6 columns:
https://github.com/lorint/duty_free/blob/master/spec/models/parent_simple_spec.rb
One nice thing about this gem is that after configuring the column definitions to do an import, you get export for free because everything works from the same template. For this example here's the template which allows the column names from your CSV to line up perfectly with the database columns:
IMPORT_TEMPLATE = {
uniques: [:firstname, :children_firstname],
required: [],
all: [:firstname, :lastname, :address, :address_line_2, :city, :province, :postal_code,
:telephone_number, :email, :admin_notes, :gross_income, :created_by_admin, :status,
{ children: [:firstname, :lastname, :dateofbirth, :gender] }],
as: {
'parent_1_firstname' => 'Firstname',
'parent_1_lastname' => 'Lastname',
'address' => 'Address',
'address_line_2' => 'Address Line 2',
'city' => 'City',
'province' => 'Province',
'postal_code' => 'Postal Code',
'telephone_number' => 'Telephone Number',
'email' => 'Email',
'admin_notes' => 'Admin Notes',
'gross_income' => 'Gross Income',
'created_by_admin' => 'Created By Admin',
'status' => 'Status',
'firstname' => 'Children Firstname',
'lastname' => 'Children Lastname',
'dateofbirth' => 'Children Dateofbirth',
'gender' => 'Children Gender'
}
}.freeze
With this in your parent.rb, you can call Parent.df_import(your_csv_object) or Parent.df_export, and the gem does the rest.
I've both googled and read other threads on the topic, and implemented all advice I could gather. But, I'm still having issues with too much memory being used (600MB+).
My users.rake file:
namespace :users do
desc "TODO"
task sync_events: :environment do
User.find_in_batches(batch_size: 5) do |users|
users.each(&:sync_events)
end
end
end
The sync_events method in user.rb model:
def sync_events
unless self.username.empty?
# Make the request to swagger
request = RestClient.get 'https://****************/event/findByUser/' + self.username + '?fromDate=' + 1.days.ago.strftime('%Y-%m-%d') + '&toDate=' + 10.days.from_now.strftime('%Y-%m-%d')
json = JSON.parse request
events = json['events']
# We need to fetch the events from the DB or create them if they don't exists
events.each do |event|
# Find existing semester or create new
semester_in_db = Semester.where(:name => event['semester'][0]['name']).first_or_create!
# Find exisiting event, or create new one.
event_in_db = Event.where(:ext_id => event['id']).first_or_create!(
:title => event['title'],
:description => event['description'],
:begin => Time.parse(event['begin']),
:end => Time.parse(event['end']),
:semester_id => semester_in_db.id
)
# Check if lectureres/users exist, or create new, and add them to the event
event['lecturer'].each do |lecturer|
# We only want to do this for users with emails - valid users
if lecturer["email"].length > 5
user = User.where(:username => lecturer['username']).first_or_create!(
:email => lecturer['username'] + '#feil.no',
:altEmail => lecturer['email'],
:password => 'password',
:password_confirmation => 'password',
:first_name => lecturer['firstname'],
:last_name => lecturer['lastname'],
:identity => lecturer['firstname'] + ' ' + lecturer['lastname']
)
#connect lecturer to event
event_in_db.users << user unless event_in_db.user_ids.include?(user.id)
#Add role
user.add_role :lecturer, event_in_db
#try to sync email, because sometimes email is not formatted correctly
begin
user.email = lecturer['email']
user.save!
rescue
e = Error.new
e.body = lecturer['email']
e.save
end
end
end
# Added student user
event_in_db.users << self unless event_in_db.user_ids.include?(self.id)
# add role student, unless lecturer
self.add_role :student, event_in_db unless self.has_role? :lecturer, event_in_db
end
end
end
You can probably see that I'm not a trained ruby developer. This code is headed for some major refactoring. But, in the mean time, I can't see why it's hogging all my memory. I've got about 500+ users in the DB. Eventually, my rake task gets killed.
As you can see, I'm already using find_in_batches instead of all. I've also converted all instance variables to local variables.
What more can I do?
I am trying out seeds.rb for the first time, and one of my data models uses encapsulation provided by the money gem.
Relevant gems:
money (3.6.1)
rails (3.0.5)
My model thus far:
app/models/list.rb
class List < ActiveRecord::Base
attr_accessible :alias, :unit, :participating_manufacturer, :quantity
:latest_price_cents, :latest_price_currency, :url
belongs_to :user
composed_of :latest_price,
:class_name => "Money",
:mapping => [%w(latest_price_cents latest_price_cents), %w(latest_price_currency currency_as_string)],
:constructor => Proc.new {
|latest_price_cents, latest_price_currency| Money.new(latest_price_cents ||
0, latest_price_currency || Money.default_currency)
},
:converter => Proc.new {
|value| value.respond_to?(:to_money) ? value.to_money : raise(ArgumentError,
"Can't convert #{value.class} to Money")
}
end
1) (Addressed successfully)
2) When I get to writing validations, would it be best to write them for the :latest_price attribute or for the :latest_price_cents & :latest_price_currency attributes seperately?
/db/seeds.rb
users = User.create([{ :name => "Foo", :email => "foo#gmail.com",
:password => "foobar", :password_confirmation => "foobar" }])
# etc, will add more users to the array
list = List.create(:user_id => users.first.id, :alias => "Januvia 100mg",
:unit => "tablet", :participating_manufacturer => "Merck",
:quantity => 30, :latest_price_cents => 7500,
:latest_price_currency => "USD", :url =>
"http://www.foobar.com/januvia/100mg-tablets/")
3) Perhaps it is minutiae, but in the seed, should I be assigning values to the virtual :latest_price attribute or to the latest_price_cents and latest_price_currency attributes directly? Is there any way to use faker rather than /db/seeds.rb to perform this task?
I am new to rails and web development.
I can't see your latest_price attribute anywhere, so I'm not sure how to answer your question. Generally, you should validate the attributes entered in the user form. So if a user enters latest_price_cents and latest_price_currency in a form, then they're the ones which need validating.
There's a bug in your seed file. You want to pass in a hash, not an array, when creating a new user; and users should be an array.
users = []
users << User.create!(:name => "Foo",
:email => "foo#gmail.com",
:password => "foobar",)
:password_confirmation => "foobar")
However, if you're considering faker because you want to create some dummy data, take a look at Machinist or Factory Girl. They're designed for creating dummy data, normally for automated tests.
Once you've set up some blueprints, if you want to create dummy data in your seeds file, you can do something like this in seeds.rb:
20.times { List.make } unless Rails.env.production?
How can I change this :project_pages_id => 1 value to auto increment?
user.projects.create!(:title => Faker::Lorem.sentence(1), :project_pages_id => 1)
10.times do |n|
user.projects.create!(:title => Faker::Lorem.sentence(1), :project_pages_id => n
end
You'd need to iterate over an array like:
a = (1..10).to_a #or however many ID's you want.
a.each do {|d| user.projects.create!(:title => Faker::Lorem.sentence(1), :project_pages_id => d)}
I'm sure there is other ways, but this is quick and dirty, and it's only a test.
Is that project_pages_id intended to be a foreign key? If so, why would you auto-increment it such that it will have a nil association?
It looks like you're trying to create seed data. A good way to do that is to use Factory Girl:
https://github.com/thoughtbot/factory_girl
Among other things, it has the concept of "sequences", which solves your original question:
# Defines a new sequence
Factory.sequence :email do |n|
"person#{n}#example.com"
end
Factory.next :email
# => "person1#example.com"
Factory.next :email
# => "person2#example.com"
Rake db:seed populates your db with default database values for an app right? So what if you already have a seed and you need to add to it(you add a new feature that requires the seed). In my experience, when I ran rake db:seed again, it added the existing content already so existing content became double.
What I need is to add some seeds and when ran, it should just add the newest ones, and ignore the existing seeds. How do I go about with this? (the dirty, noob way I usually do it is to truncate my whole db then run seed again, but that's not very smart to do in production, right?)
A cleaner way to do this is by using find_or_create_by, as follows:
User.find_or_create_by_username_and_role(
:username => "admin",
:role => "admin",
:email => "me#gmail.com")
Here are the possible outcomes:
A record exists with username "admin" and role "admin". This record will NOT be updated with the new e-mail if it already exists, but it will also NOT be doubled.
A record does not exist with username "admin" and role "admin". The above record will be created.
Note that if only one of the username/role criteria are satisfied, it will create the above record. Use the right criteria to ensure you aren't duplicating something you want to remain unique.
I do something like this.... When I need to add a user
in seeds.rb:
if User.count == 0
puts "Creating admin user"
User.create(:role=>:admin, :username=>'blagh', :etc=>:etc)
end
You can get more interesting than that, but in this case, you could run it over again as needed.
Another option that might have a slight performance benefit:
# This example assumes that a role consists of just an id and a title.
roles = ['Admin', 'User', 'Other']
existing_roles = Role.all.map { |r| r.title }
roles.each do |role|
unless existing_roles.include?(role)
Role.create!(title: role)
end
end
I think that doing it this way, you only have to do one db call to get an array of what exists, then you only need to call again if something isn't there and needs to be created.
Adding
from
departments = ["this", "that"]
departments.each{|d| Department.where(:name => d).first_or_create}
to
departments = ["this", "that", "there", "then"]
departments.each{|d| Department.where(:name => d).first_or_create}
this is a simple example,
Updating/rename
from
departments = ["this", "that", "there", "then"]
departments.each{|d| Department.where(:name => d).first_or_create}
to
departments = ["these", "those", "there", "then"]
new_names = [['these', 'this'],['those','that']]
new_names.each do |new|
Department.where(:name => new).group_by(&:name).each do |name, depts|
depts.first.update_column :name, new[0] if new[1] == name # skips validation
# depts[1..-1].each(&:destroy) if depts.size > 1 # paranoid mode
end
end
departments.each{|d| Department.where(:name => d).first_or_create}
IMPORTANT: You need to update the elements of departments array else duplication will surely happen.
Work around: Add a validates_uniqueness_of validation or a validation of uniqueness comparing all necessary attributes BUT don't use methods skipping validations.
My preference for this sort of thing is to create a custom rake task rather than use the seeds.rb file.
If you're trying to bulk create users I'd create a .csv files with the data then create a rake task called import_users and pass it the filename. Then loop through it to create the user records.
In lib/tasks/import_users.rake:
namespace :my_app do
desc "Import Users from a .csv"
task :import_users => :environment do
# loop through records and create users
end
end
Then run like so: rake bundle exec my_app:import_users path/to/.csv
If you need to run it in production: RAILS_ENV=production bundle exec rake my_app:import_users /path/to/.csv
Another alternative is to use the #first_or_create.
categories = [
[ "Category 1", "#e51c23" ],
[ "Category 2", "#673ab7" ]
]
categories.each do |name, color|
Category.where( name: name, color: color).first_or_create
end
A really hackable way would be to comment out the existing data, that's how i did it, and it worked fine for me
=begin
#Commented Out these lines since they where already seeded
PayType.create!(:name => "Net Banking")
PayType.create!(:name => "Coupouns Pay")
=end
#New data to be used by seeds
PayType.create!(:name => "Check")
PayType.create!(:name => "Credit card")
PayType.create!(:name => "Purchase order")
PayType.create!(:name => "Cash on delivery")
Once done just remove the comments
Another trivial alternative:
#categories => name, color
categories = [
[ "Category 1", "#e51c23" ],
[ "Category 2", "#673ab7" ]
]
categories.each do |name, color|
if ( Category.where(:name => name).present? == false )
Category.create( name: name, color: color )
end
end
Just add User.delete_all and for all the models that you have included in your application at the beginning of your seed.rb file. There will not be any duplicate values for sure.