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!
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.
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.
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.
Remove your def initialize() method
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.
Related
Has anybody gotten Ransack to search Gutentag tag_names ? I have had issues making ActsAsTaggableOn work but Gutentag works like a charm, I just need to be able to search on it..
resource.tag_names returns a list of tags for resource.
I have in my model
def self.ransackable_attributes(auth_object = nil)
['title','content', 'tag_names']
end
the line erroring is #resources = #q.result(distinct: true).paginate(:page => params[:page])
I'm assuming that tag_names is not returning the right data for Ransack. Any ideas?
This happens because there is no column tag_names in your table. Thus you need to define ransackable_attributes for gutentag_tags table, because that is the table whose name column is called under the hood. Create an initializer for gutentag in config/initializers directory, name it however you like. In that initializer put the following:
if ActiveRecord::Base.connection.table_exists? 'gutentag_tags'
model = Object.const_get "Gutentag::Tag"
model.singleton_class.class_eval do
define_method(:ransackable_attributes) do |auth_object|
auth_object = nil
['name']
end
end
end
This should do the trick although I haven't tried it myself. Feel free to ask anything in case you meet problems with this piece of code.
I'm using the rails-footnotes gem in my Rails 3.2 applications, but I can't seem to get the footnotes to register the existence of any partials: it always shows a zero partial count.
To be able to know how many and what partials are being displayed on a view easily like this is, I think, immensely useful, so I would really like to get this working (the rest works great), but I'm not sure what the problem is and am hoping someone else has had the same problem and was able to resolve it. Is there a setting I've potentially missed?
I don't think it's relevant, but I'm using OSX 10.6.8 and had some issues getting the gem to work with Sublime Text 2 properly, but they did get resolved (details in this StackOverflow answer).
Update:
It seems that the issue only exists for haml templates, as I am getting expected output for erb templates. It would seem that only erb templates are counted/recognized...?
Update 2:
#DonHopkins' answer below got all my Haml templates to register with Rails Footnotes. I put it in my config file as follows:
config/initializers/rails_footnotes.rb
if defined?(Footnotes) && Rails.env.development?
Footnotes.run! # first of all
Footnotes::Notes::LogNote::LoggingExtensions.module_eval do
def add(*args, &block)
super
logged_message = args[2] + "\n"
Footnotes::Notes::LogNote.log(logged_message)
logged_message
end
end
# ... other init code
Footnotes::Filter.prefix = 'subl://open?url=file://%s&line=%d&column=%d'
end
I had a similar problem, although I am using erb templates, not haml. I fixed it with a monkey patch to rails-footnotes.
Looking at the rails-footnotes code (version 3.7.9), it looked to me like the problem is in this method:
module Footnotes
module Notes
class LogNote < AbstractNote
...
module LoggingExtensions
def add(*args, &block)
logged_message = super
Footnotes::Notes::LogNote.log(logged_message)
logged_message
end
end
...
end
end
end
The add method is assuming that super returns the message that is being logged, but in my testing super was returning a boolean value. To solve the problem, I created a file called footnotes_patch.rb with the following:
Footnotes::Notes::LogNote::LoggingExtensions.module_eval do
def add(*args, &block)
super
logged_message = args[2] + "\n"
Footnotes::Notes::LogNote.log(logged_message)
logged_message
end
end
If you want to try the solution, put that file in config/initializers, then restart your application.
This is probably one of the things that all new users find out about Rails sooner or later. I just realized that rails is updating all fields with the serialize keyword, without checking if anything really changed inside. In a way that is the sensible thing to do for the generic framework.
But is there a way to override this behavior? If I can keep track of whether the values in a serialized fields have changed or not, is there a way to prevent it from being pushed in the update statement? I tried using "update_attributes" and limiting the hash to the fields of interest, but rails still updates all the serialized fields.
Suggestions?
Here is a similar solution for Rails 3.1.3.
From: https://sites.google.com/site/wangsnotes/ruby/ror/z00---topics/fail-to-partial-update-with-serialized-data
Put the following code in config/initializers/
ActiveRecord::Base.class_eval do
class_attribute :no_serialize_update
self.no_serialize_update = false
end
ActiveRecord::AttributeMethods::Dirty.class_eval do
def update(*)
if partial_updates?
if self.no_serialize_update
super(changed)
else
super(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
super
end
end
end
Yes, that was bugging me too. This is what I did for Rails 2.3.14 (or lower):
# config/initializers/nopupdateserialize.rb
module ActiveRecord
class Base
class_attribute :no_serialize_update
self.no_serialize_update = false
end
end
module ActiveRecord2
module Dirty
def self.included(receiver)
receiver.alias_method_chain :update, :dirty2
end
private
def update_with_dirty2
if partial_updates?
if self.no_serialize_update
update_without_dirty(changed)
else
update_without_dirty(changed | (attributes.keys & self.class.serialized_attributes.keys))
end
else
update_without_dirty
end
end
end
end
ActiveRecord::Base.send :include, ActiveRecord2::Dirty
Then in your controller use:
model_item.no_serialize_update = true
model_item.update_attributes(params[:model_item])
model_item.increment!(:hits)
model_item.update_attribute(:nonserializedfield => "update me")
etc.
Or define it in your model if you do not expect any changes to the serialized field once created (but update_attribute(:serialized_field => "update me" still works!)
class Model < ActiveRecord::Base
serialize :serialized_field
def no_serialize_update
true
end
end
I ran into this problem today and ended up hacking my own serializer together with a getter and setter. First I renamed the field to #{column}_raw and then used the following code in the model (for the media attribute in my case).
require 'json'
...
def media=(media)
self.media_raw = JSON.dump(media)
end
def media
JSON.parse(media_raw) if media_raw.present?
end
Now partial updates work great for me, and the field is only updated when the data is actually changed.
The problem with Joris' answer is that it hooks into the alias_method_chain chain, disabling all the chains done after (like update_with_callbacks which accounts for the problems of triggers not being called). I'll try to make a diagram to make it easier to understand.
You may start with a chain like this
update -> update_with_foo -> update_with_bar -> update_with_baz
Notice that update_without_foo points to update_with_bar and update_without_bar to update_with_baz
Since you can't directly modify update_with_bar per the inner workings of alias_method_chain you might try to hook into the chain by adding a new link (bar2) and calling update_without_bar, so:
alias_method_chain :update, :bar2
Unfortunately, this will get you the following chain:
update -> update_with_bar2 -> update_with_baz
So update_with_foo is gone!
So, knowing that alias_method_chain won't let you redefine _with methods my solution so far has been to redefine update_without_dirty and do the attribute selection there.
Not quite a solution but a good workaround in many cases for me was simply to move the serialized column(s) to an associated model - often this actually was a good fit semantically anyway.
There is also discussions in https://github.com/rails/rails/issues/8328.
I want to be able to upload an Excel file that contains contact information. I then went to be able to parse it and create records for my Contact model.
My application is a Rails application.
I am using the paperclip gem on heroku, I've been able to parse vim cards into the Contact model, and am looking for something similar, but will go through all lines of the Excel file.
Gems that simplify the task and sample code to parse would be helpful!
Spreadsheet is the best Excel parser that I have found so far. It offers quite a lot of functionality.
You say you use Paperclip for attachments which is good. However, if you store the attachments in S3 (which I assume since you use Heroku) the syntax for passing the file to spreadsheet is a little different but not difficult.
Here is an example of the pure syntax that can be used and not placed in any classes or modules since I don't know how you intend to start the parsing of contacts.
# load the gem
require 'spreadsheet'
# In this example the model MyFile has_attached_file :attachment
#workbook = Spreadsheet.open(MyFile.first.attachment.to_file)
# Get the first worksheet in the Excel file
#worksheet = #workbook.worksheet(0)
# It can be a little tricky looping through the rows since the variable
# #worksheet.rows often seem to be empty, but this will work:
0.upto #worksheet.last_row_index do |index|
# .row(index) will return the row which is a subclass of Array
row = #worksheet.row(index)
#contact = Contact.new
#row[0] is the first cell in the current row, row[1] is the second cell, etc...
#contact.first_name = row[0]
#contact.last_name = row[1]
#contact.save
end
I had a similar requirement in one of my Rails 2.1.0 application. I solved it in the following manner:
In the 'lib' folder I wrote a module like this:
require 'spreadsheet'
module DataReader
def read_bata(path_to_file)
begin
sheet = book.worksheet 0
sheet.each 2 do |row|
unless row[0].blank?
# Create model and save it to DB
...
end
end
rescue Exception => e
puts e
end
end
end
Had a model Upload:
class Upload < AR::Base
has_attached_file :doc,
:url => "datafiles/:id",
:path => ":rails_root/uploads/:id/:style/:basename.:extension"
# validations, if any
end
Generated an UploadsController which would handle the file upload and save it to appropriate location. I used Paperclip for file upload.
class UploadsController < AC
include DataReader
def new
#upload = Upload.new
end
def create
#upload = Upload.new(params[:upload])
#upload.save
file_path = "uploads/#{#upload.id}/original/#{#upload.doc_file_name}"
#upload.read = DataReader.read_data(file_path)
# respond_to block
end
end
Read about 'spreadsheet' library here and here. You can make appropriate improvements and make the technique work in Rails 3. Hope this helps.
I made a gem to achieve this easily. I called it Parxer and...
It's built on to of roo gem.
It allows you to parse xls, xlsx and csv files.
Has a DSL to handle:
Column mapping.
File, row, and column/cell validation.
Column/cell formatting.
I have the following which works great:
#request_thread = current_user.request_threads.new(params[:request_thread])
And I have this which works great:
#requestable = find_requestable
#request_thread = #requestable.request_threads.new(params[:request_thread])
But if I try the 2nd line with:
#request_thread = #requestable.current_user.request_threads.new(params[:request_thread])
With the current_user I get the following error:
NoMethodError (undefined method `current_user' for #<Photo:0x10f95c828>):
app/controllers/request_threads_controller.rb:52:in `create'
app/middleware/flash_session_cookie_middleware.rb:14:in `call'
What did I mess up on here?
Thanks
UPDATE - with find_requestable
# http://asciicasts.com/episodes/154-polymorphic-association
def find_requestable
params.each do |name, value|
if name =~ /(.+)_id$/
return $1.classify.constantize.find(value)
end
end
nil
end
The error message tells you exactly whats wrong: current_user doesn't exist for the #requestable object, whatever that is.
current_user is most likely a function inherited from ApplicationController, or at least that's usually where it lives. It usually returns a User object according to the current session. But that isn't a built-in part of Rails, so we need more information if you want me to go into greater detail.
#requestable looks like a polymorphic model instance so it wouldn't be aware of the current session.
There's a bit of Rails magic happening here that I think is confusing you.
Both #requestable.request_threads and current_user.request_threads are helper functions that have been generated by Rails on those objects to save you time by filtering results and filling in values automatically.
I think, by the code you have shown us so far, that you are trying to associate current_user with the new request_thread you are creating. In that case, you can simple merge that into the attributes manually. Since I don't know what your models look like I can only guess at the field names:
#request_thread = #requestable.request_threads.new( params[:request_thread].merge(:user => current_user) )
Like I said, the chainable functions are merely for convenience. For instance, you could write it this way instead:
#request_thread = request_thread.new(params[:request_thread])
#request_thread.requestable = find_requestable
#request_thread.user = current_user