0

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,[email protected],"HAPPY",13917, TRUE , Approved ,Sami,Kidane,2009-10-10,Male
5
  • Where is find_or_update_or_create_by defined? Commented Sep 19, 2018 at 17:36
  • @jvillian it is in the parent model as shown above. Commented Sep 19, 2018 at 18:02
  • Show the Parent model, please. Specifically, the definition of the find_or_update_or_create_by class method. Commented Sep 19, 2018 at 18:09
  • @jvillian arent these built-in class methods in rails? I have not defined them separately. Commented Sep 19, 2018 at 18:23
  • No, find_or_update_or_create_by is not a built-in class method. That is why you're getting the undefined method error (as Pavan has indicated). Commented Sep 19, 2018 at 18:29

2 Answers 2

1

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.

Sign up to request clarification or add additional context in comments.

4 Comments

If I try what you suggested below is the error I am getting You cannot call create unless the parent is saved
You cannot call create unless the parent is saved parent.children.find_or_create_by( firstname: row['firstname'], lastname: row['lastname'], dateofbirth: row['dateofbirth'], gender: row['gender'] ) end end
You probably have a validation error. Try find_or_create_by! which will raise an error if there is a problem with your parent model.
It started working now. Idk how.may be csv data is not arranged properly
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.