4

I am trying to use Roo to import data from an Excel spreadsheet into a table (data_points) in a Rails app.

I am getting the error:

undefined method `fetch_value' for nil:NilClass

and that error references my data_point.rb file at line (see below for full code excerpt):

data_point.save!

The "Application Trace" says:

app/models/data_point.rb:29:in `block in import'
app/models/data_point.rb:19:in `import'
app/controllers/data_points_controller.rb:65:in `import'

I am puzzled by this because a "find all" in my entire app shows no instance of fetch_value

Here is the other code in my app:

In my model, data_point.rb:

class DataPoint < ActiveRecord::Base
attr_accessor :annual_income, :income_percentile, :years_education

def initialize(annual_income, income_percentile, years_education)
    @annual_income = annual_income
    @income_percentile = income_percentile
    @years_education = years_education
end

def self.import(file)
    spreadsheet = open_spreadsheet(file)
    header = spreadsheet.row(1)
    (2..11).each do |i| 
        annual_income = spreadsheet.cell(i, 'A')
        income_percentile = spreadsheet.cell(i, 'B')
        years_education = spreadsheet.cell(i, 'C')
        data_point = DataPoint.new(annual_income, income_percentile, years_education)
        data_point.save!
    end
end 

def self.open_spreadsheet(file)
    case File.extname(file.original_filename)
    when ".xlsx" then Roo::Excelx.new(file.path)
    else raise "Unknown file type: #{file.original_filename}"
    end
end
end

In my controller, data_points_controller.rb I have added, in addition to the standard rails framework:

def import
    DataPoint.import(params[:file])
    redirect_to root_url, notice: "Data points imported."
end

In the Excel file I'm using, the header row has column names that are exactly the same as the 3 attributes noted above for DataPoints: annual_income, income_percentile, years_education

P.s. I have already watched RailsCast 396: Importing CSV and Excel, and read the comments many times. I think I am struggling with translating the example code to Rails 4 and / or my assignment of individual attributes (vs. the approach used in the RailsCast).

Thanks in advance for any help!

WallE
  • 83
  • 1
  • 1
  • 9
  • 1
    `fetch_value` is an ActiveRecord method. It's one of the things that gets used when you do `model.save!` As for why you're getting, I'm not sure on that. The only thing I can see that uses `fetch_value` is a method calls `_read_attribute` which does `@attributes.fetch_value` So `@attributes` is `nil`. It might be your `DataPoint.new(annual_income, income_percentile, years_education)`. Try `DataPoint.new(annual_income: annual_income, etc...)` – Clark Jun 02 '15 at 17:18
  • Thanks, when I try data_point = DataPoint.new(annual_income: annual_income, income_percentile: income_percentile, years_education: years_education) I get an error wrong number of arguments (1 for 3)... is something about my syntax wrong that Rails is reading this as 1 rather than 3 attributes? – WallE Jun 02 '15 at 17:29
  • 1
    It is interpreting it as a hash of attributes, which it should be doing. Is your `new` method setup so it takes each attribute as an argument rather than `.new({attributes hash}, {options hash})`? Nevermind, I see your initialize method overwrites it. – Clark Jun 02 '15 at 17:51
  • Okay, see see what the initialize method does here http://apidock.com/rails/ActiveRecord/Base/new/class You're overwriting it so everything it does is not done any more. (click view source right after the examples, before the comments) – Clark Jun 02 '15 at 17:53
  • Thanks, when I delete the initialize method I am able to create 10 instances of DataPoint (as expected) -- my use of initialize was a vestige of when I was practicing in a .rb file, no rails app. However, I now have the problem that all the attributes of each DataPoint are nil. Even when I use the syntax you suggest above, I cannot set the values. Please let me know if you have any idea what might be wrong. Thanks again! – WallE Jun 02 '15 at 18:45
  • Hmm, `.new` would give an error if you set an attribute does not exist, and `.save!` should give an error if it fails. Is there no chance that the values it is getting are `nil` or nothing? If you change the `.new(etc..)` to `DataPoint.new(annual_income: '10', income_percentile: '5', years_education: '4')` does that work? – Clark Jun 02 '15 at 18:54
  • I thought that explicitly specifying the values should work, too, but oddly it does not. The same Excel file can be read just fine with the practice.rb file...unclear what's different "inside the rails app".. – WallE Jun 02 '15 at 19:30
  • That is very odd. Does your server console show any SQL being executed? – Clark Jun 02 '15 at 19:39
  • Yes, it does: (0.1ms) begin transaction SQL (0.5ms) INSERT INTO "data_points" ("created_at", "updated_at") VALUES (?, ?) [["created_at", "2015-06-02 19:39:43.856012"], ["updated_at", "2015-06-02 19:39:43.856012"]] (2.3ms) commit transaction (0.1ms) begin transaction – WallE Jun 02 '15 at 19:44
  • So it is definitely not passing the values. If you add `puts data_point.inspect` after `new`, but before `save` does it show `nil`s or actual values? – Clark Jun 02 '15 at 19:48
  • nils # (0.1ms) begin transaction – WallE Jun 02 '15 at 20:02
  • I think it's the `attr_accessor :annual_income, :income_percentile, :years_education` does it work if you remove this? – Clark Jun 02 '15 at 20:04
  • YES! That worked! THANK YOU! How did you know that / why did that work? – WallE Jun 02 '15 at 20:18
  • 1
    That was the only difference between your model, and one I had, so I tried adding it to one of my attributes, and... `nil`. Because you're connecting to the database, all attributes are already accessible. Anything you make an `attr_accessible` is extra data you want to attach to that model instance, but don't want in the database. This is of course a problem if you use the same name for both. – Clark Jun 02 '15 at 20:20
  • awesome, thanks again! – WallE Jun 02 '15 at 20:32
  • Glad to help. I'll put this in an answer so you can mark it as answered. – Clark Jun 02 '15 at 20:37

5 Answers5

23

I ran into a similar error also when trying to use Active Record with a legacy database. The problem for me was related to the fact that one of the columns of my database was named 'class,' which caused all sorts of things to fail. I renamed the column in the legacy database and everything worked fine.

Moral of the story- check the column names for any reserved words.

Mickey Sheu
  • 758
  • 7
  • 16
10

It seems you had some leftovers from your non rails practice, as we found in the comments. Notably, the overwritten initialize method, and the attr_accessor for each of the attributes. Removing them (and fixing the DataPoint.new() for the correct format) was all that was needed.

Clark
  • 616
  • 4
  • 7
8

This is the problem encountered yesterday, the reason is I defined a Database field with name class, which is a reserved word for the Ruby language. It caused the conflict.

David Zhang
  • 468
  • 7
  • 10
1

Remove your def initialize() method

jeff porter
  • 6,560
  • 13
  • 65
  • 123
0

If you are not restricted to delete the initialize method for some reason, instead of removing it, you can try to add a super call like so:

def initialize(annual_income, income_percentile, years_education)
  super()

  @annual_income = annual_income
  @income_percentile = income_percentile
  @years_education = years_education
end

Sources:

  • More info about super can be found here.
Marian13
  • 7,740
  • 2
  • 47
  • 51