How to deal with eval nil issues in Rails - ruby-on-rails

I am facing a pretty nasty error in a production environment that caused a bit of confusion. I increased logging on production to narrow down the issue and was now able to reproduce it on our local environment. So setup wises we are running on Rails 5, on a Ubuntu machine. The application uses ElasticSearch 5.4, and we are storing images on Amazon S3.
Process: A user can upload images. There is a User Index in Elasticsearch that also stores information to the related Photo model.
The issue is that once a user uploads a photo, it sometimes comes to an error on an index page or detailed user page where photos are in one way or another accessed. And the results to be displayed are not read from a database but Elasticsearch.
Workaround (which isn't one). Once the index is getting reimported the error doesn't occur any longer - which initially led me to believe that it has something to do with ElasticSearch.
User Model
class User < ApplicationRecord
include UserSearchable
extend FriendlyId
require "redis"
friendly_id :slug_candidates, use: :slugged
has_many :photos
after_update { self.photos.each(&:touch) }
...
end
User Searchable Concern
module UserSearchable
extend ActiveSupport::Concern
included do
include Elasticsearch::Model
include Elasticsearch::Model::Callbacks
index_name Rails.application.class.parent_name.underscore
document_type self.name.downcase
settings index: { number_of_shards: 1 } do
mapping dynamic: false do
indexes :description, analyzer: 'english'
indexes :tagline, analyzer: 'english'
indexes :username
...
end
end
after_touch() { __elasticsearch__.index_document }
def as_indexed_json(_options = {})
self.as_json(
except: [:email, :lat, :lng, :status, :termsofuse, :v_code],
include: {
photos: { only: [:name, :caption, :active, :image_data, :downloadable, :uploader_id, :public] },
).merge(
location: {lat: lat.to_f, lon: lng.to_f},
age: birthday.nil? ? 18 : ((Date.today - birthday.to_date) / 365.25).floor
)
end
def home_search(searcher, order = nil, how_many = nil)
how_many = 400 unless how_many.is_a?(Integer)
order = 1 unless order.is_a?(Integer)
if self.radius < 30
use_radius = 30
else
use_radius = self.radius
end
search_definition = Jbuilder.encode do |json|
json.sort do
if order == 1
json.array! [{'_geo_distance' => { :location => {:lat => lat, :lon => lng} }}, '_score']
else
json.array! ['_score', {'_geo_distance' => { :location => {:lat => lat, :lon => lng} }}]
end
end
json.query do
json.bool do
json.filter do
json.bool do
json.must do
json.array! [:geo_distance => { :distance => use_radius, :unit => "mi", :location => {:lat => self.lat, :lon => self.lng}, :boost => 5.0}]
end
...
unless searcher.id.nil?
json.must_not do
json.array! [ {:term => { 'id' => self.id }} ]
end
end
end
end
...
end
end
# json.size how_many
end
self.class.__elasticsearch__.search(search_definition)
end
end
end
Photo Model
class Photo < ApplicationRecord
include ImageUploader[:image]
include ActiveModel::Validations
acts_as_taggable
before_create :set_name
belongs_to :user, touch: true
after_update { self.user(&:touch) }
private
def set_name
self.name = "Photo"
end
end
Tests of issue in console:
Here, and this is key to the problem. If I eval for the photo sometimes it is nil, and sometimes it is not! And I cannot figure out why? If I run this command lets say 10 times on console, it is nil 2 out of 10 times. And that is when the production system is thrown off its track and the user is presented with an error. As the system is intranet and new we actually show exceptions to users (so do not be alarmed by bad exception handling - users don't say a thing if you don't make it clear ;) )
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
Additional trace:
>> Settings.s3 + eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>>
=> nil
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
>> user.photos.sample.image_data
=> "{\"original\":{\"id\":\"photo/47/image/original-114c9db755b25afe0398f5b25aed5bef.jpg\",\"storage\":\"store\",\"metadata\":{\"size\":61357,\"filename\":\"London-Escort-Angelina (5).jpg\",\"mime_type\":\"image/jpeg\",\"width\":500,\"height\":500}},\"large\":{\"id\":\"photo/47/image/large-c3985d412ee05495594caa659feca371.jpg\",\"storage\":\"store\",\"metadata\":{\"filename\":\"shrine-s320170627-29702-17km6c5.jpg\",\"size\":61356,\"mime_type\":\"image/jpeg\",\"width\":500,\"height\":500}},\"small\":{\"id\":\"photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg\",\"storage\":\"store\",\"metadata\":{\"filename\":\"shrine-s320170627-29702-ouo63f.jpg\",\"size\":25642,\"mime_type\":\"image/jpeg\",\"width\":300,\"height\":300}}}"
>> user.photos.sample.image_data[:small]
!! #<TypeError: no implicit conversion of Symbol into Integer>
>> eval(user.photos.sample.image_data)[:small]
=> {:id=>"photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg", :storage=>"store", :metadata=>{:filename=>"shrine-s320170627-29702-ouo63f.jpg", :size=>25642, :mime_type=>"image/jpeg", :width=>300, :height=>300}}
>> eval(user.photos.sample.image_data)[:small][:id]
=> "photo/47/image/small-2cd0928d02826f0614086a01ee97ef32.jpg"
>> eval(user.photos.sample.image_data)[:small][:id]
!! #<NoMethodError: undefined method `[]' for nil:NilClass>
Any help is much appreciated!!!

Your results are different because you use sample method, whitch choose a random element from user.photos.
For prevent undefined method [] for nil:NilClass error you can use &.dig for ruby >= 2.3 or try if less
nil&.dig(:small, :id)
=> nil
nil.try(:dig, :small, :id)
=> nil

So, I could resolve the issue (workaround-ish) by reindexing the updated user document when a photo is uploaded or deleted. Forgot about the destroy part before. It doesn't seem right as it is messy given an entire record must be removed in ES and reindexed, but traffic is low and for the time being it works. If I figure out the issue, I will revert to this thread.

Related

Split gem dashboard actions don't work

When trying to reset experiments in the Split gem for a/b testing, I keep getting server errors:
NoMethodError at /_split/reset/listing_headline
undefined method `reset' for nil:NilClass
file: dashboard.rb location: block in <class:Dashboard> line: 31
Any ideas?
Maybe not in your case, but I've seen this when someone was using symbols as test names instead of strings.
example: don't use:
:myTest => {}
use:
"myTest" => {
:alternatives => [
{ :name => 'default', :percent => 50 },
{ :name => 'alt', :percent => 50 },
],
:resettable => false
:metric => :lead,
}

Deprecation warning for creating attribute 'currency'

I'm using Rails 3.2.3 with the money-rails gem and I've got a product model which has the following:
My model
class Product < ActiveRecord::Base
attr_accessible :name, :price
composed_of :price,
:class_name => "Money",
:mapping => [%w(price_cents cents), %w(currency currency_as_string)],
:constructor => Proc.new { |cents, currency| Money.new(cents || 0, 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
My Test
require 'spec_helper'
describe Product do
context "testing money gem" do
it "creates product with price" do
product = Product.create(:price => 200)
product.price.should eq(200)
product.price_cents.should eq(20000)
end
end
end
Deprecation warning I'm getting.
% rspec spec/models/product_spec.rb
Product
testing money gem
DEPRECATION WARNING: You're trying to create an attribute `currency'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc. (called from block (3 levels) in <top (required)> at /home/map7/project/spec/models/product_spec.rb:6)
creates product with price
Finished in 0.06682 seconds
1 example, 0 failures
How do I fix this deprecation warning?
Update
If I add 'currency' to the table it starts working. Should I have to do this though?
Apparently in Rails 3.2 and above arbitrary attributes (attributes not stored in the database) are no longer allowed. There doesn't seem to be a way around it.
Here is the commit for the deprecation message: https://github.com/rails/rails/commit/b2955edc
and here is why: https://github.com/rails/rails/commit/50d395f96ea05da1e02459688e94bff5872c307b
In your case price_cents and currency still need to be stored in the database and then your composed class will take it from there.
Added 'currency:string' to my model

Feedzirra in Rails 3

I am trying to get feedzirra running on rails 3, I tried by some methods I have found on the internet.
This is in my gemfile:
source 'http://gems.github.com'
gem 'loofah', '1.0.0.beta.1'
group :after_initialize do
gem 'pauldix-feedzirra'
end
And i've out this after bundle.setup in root.rb
Bundler.require :after_initialize
And this is the code in my model (movie.rb)
class Movie < ActiveRecord::Base
def self.import_from_feed
feed = Feedzirra::Feed.fetch_and_parse("url-to.xml")
add_entries(feed.entries)
end
private
def self.add_entries(entries)
entries.each do |entry|
unless exists? :guid => entry.id
create!(
:title => entry.title,
:synopsis => entry.synopsis,
:cover => entry.cover,
:duration => entry.duration,
:channel => entry.channel,
:imdb_rating => entry.imdb_rating,
:imdb_votes => entry.imdb_votes,
:imdb_id => entry.imdb_votes
)
end
end
end
end
I try to run the import_from_feed function from the console and I keep getting this error:
>> Movie.import_from_feed
NameError: uninitialized constant Movie::Feedzirra
from /Users/myname/Ruby/appname/app/models/movie.rb:3:in `import_from_feed'
from (irb):1
Can someone help me out? Been trying for ages now!
Two things:
Just add the gem, not under :after_initialize
Use the feedzirra gem, not the old pauldix-feedzirra one.

multiple paperclip attachements with watermark processor

I'm having a ton of trouble with uploading multiple attachments with paperclip and processing them with a watermark.
I have 2 models, Ad and Photo.
The Ad has_many :photos and the Photo belongs_to :ad.
To be more exact in /models/ad.rb I have:
class Ad < ActiveRecord::Base
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos, :allow_destroy => true
end
and my photo.rb file looks like this:
class Photo < ActiveRecord::Base
belongs_to :ad
has_attached_file :data,
:styles => {
:thumb => "100x100#",
:first => {
:processors => [:watermark],
:geometry => '300x250#',
:watermark_path => ':rails_root/public/images/watermark.png',
:position => 'SouthEast' },
:large => {
:processors => [:watermark],
:geometry => '640x480#',
:watermark_path => ':rails_root/public/images/watermark.png',
:position => 'SouthEast' }
}
end
In my view I use this to add the file fields
<% f.fields_for :photos do |p| %>
<%= p.label :data, 'Poza:' %> <%= p.file_field :data %>
<% end %>
In my controller, in the edit action i use 4.times {#ad.photos.build} to generate the file fields.
It all works fine and dandy if I don't use the watermark processor, if I use a normal has_attached_file declaration, like this:
has_attached_file :data,
:styles => {
:thumb => "100x100#",
:first => '300x250#',
:large => '640x480#'
}
But when I use the watermark processor I always get this error:
NoMethodError in PublicController#update_ad
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
..............................
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:350:in `assign_nested_attributes_for_collection_association'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `each'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:345:in `assign_nested_attributes_for_collection_association'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/nested_attributes.rb:243:in `photos_attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `send'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2746:in `attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `each'
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2742:in `attributes='
/usr/lib/ruby/gems/1.8/gems/activerecord-2.3.5/lib/active_record/base.rb:2628:in `update_attributes'
/home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:217
/home/alexg/Sites/vandmasina/app/controllers/public_controller.rb:216:in `update_ad'
The parameters are ok, as far as I can say
Parameters:
{"commit"=>"Salveaza modificarile",
"ad"=>{"price"=>"6000",
"oras"=>"9",
"photos_attributes"=>{"0"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-b42noe-0>},
"1"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-r0ukcr-0>},
"2"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-mb23ei-0>},
"3"=>{"data"=>#<File:/tmp/RackMultipart20100928-5130-1bpkm3b-0>}},
The Watermark processor /lib/paperclip_processors/watermark.rb looks like this:
module Paperclip
class Watermark < Processor
class InstanceNotGiven < ArgumentError;
end
def initialize(file, options = {},attachment = nil)
super
#file = file
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
#watermark = ':rails_root/public/images/watermark.png'
#current_geometry = Geometry.from_file file # This is pretty slow
#watermark_geometry = watermark_dimensions
end
def watermark_dimensions
return #watermark_dimensions if #watermark_dimensions
#watermark_dimensions = Geometry.from_file #watermark
end
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
watermark = " \\( #{#watermark} -extract #{#current_geometry.width.to_i}x#{#current_geometry.height.to_i}+#{#watermark_geometry.height.to_i /
2}+#{#watermark_geometry.width.to_i / 2} \\) "
command = "-gravity center " + watermark + File.expand_path(#file.path) + " " +File.expand_path(dst.path)
begin
success = Paperclip.run("composite", command.gsub(/\s+/, " "))
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny_thumbnails
end
dst
end
end
end
I have tried the processor in a normal app, without multiple attachments and it works perfect. It doesn't work with nested_attributes as far as I can tell.
The app is rails 2.3.5 with ruby 1.8.7 and paperclip 2.3.11
If you can provide any help it would be appreciated a lot, since I've been trying to figure this out for 2 days now :)
If you use rails 3 and paperclip > 2.3.3, try https://gist.github.com/843418 source.
Oh, man, that was a tough one!
You have few errors in your code and none is related to nested models. My explanation is for Rails 3 & Paperclip 2.3.3
a) the :rails_root thing doesn't work. This interpolation is used only in url/path and not on custom options. So you should replace it with Rails.root.join("public/images...")
b) you simply ignore the :watermark_path option and you use only hardcoded path (in initialization method). So it doesn't matter what you have in your :styles as it always go for .../images/watermark.png. The :rails_root thingy there again so it cannot work.
c) when you pass a parameter to Paperclip.run("composite", "foo bar there") it actually executes this command:
composite 'foo bar there'
can you see the single quotes? Because of that the composite command see your parameters as one huge parameter and doesn't understand it at all. If you pass it as an array, then every item is enclosed in the quotes, not the array as a whole.
So here is the improved version of watermark processor:
module Paperclip
class Watermark < Processor
class InstanceNotGiven < ArgumentError;
end
def initialize(file, options = {},attachment = nil)
super
#file = file
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
# PAWIEN: use default value only if option is not specified
#watermark = options[:watermark_path] || Rails.root.join('public/images/watermark.png')
#current_geometry = Geometry.from_file file # This is pretty slow
#watermark_geometry = watermark_dimensions
end
def watermark_dimensions
return #watermark_dimensions if #watermark_dimensions
#watermark_dimensions = Geometry.from_file #watermark
end
def make
dst = Tempfile.new([#basename, #format].compact.join("."))
dst.binmode
begin
# PAWIEN: change original "stringy" approach to arrayish approach
# inspired by the thumbnail processor
options = [
"-gravity",
"center",
"#{#watermark}",
"-extract",
"#{#current_geometry.width.to_i}x#{#current_geometry.height.to_i}+#{#watermark_geometry.height.to_i / 2}+#{#watermark_geometry.width.to_i / 2}",
File.expand_path(#file.path),
File.expand_path(dst.path)
].flatten.compact.join(" ").strip.squeeze(" ")
success = Paperclip.run("composite", options)
rescue PaperclipCommandLineError
raise PaperclipError, "There was an error processing the watermark for #{#basename}" if #whiny_thumbnails
end
dst
end
end
end
Hope that helped you!
UPDATE: You need to use latest paperclip gem from github
gem 'paperclip', '>= 2.3.3', :git => "http://github.com/thoughtbot/paperclip.git"
In this version the formats of #run were changed once again so I've updated the code. It really should work as I've created test application and it's doing what supposed.
UPDATE 2: Repo with working example:
git://repo.or.cz/paperclip-mass-example.git
Just from a quick glance it looks like watermark_path should be "#{Rails.root}/..." though it looks like you have a lot going on here.
Also, I don't see your form as in form_for. Make sure you have {:multipart => true}

Rails: Caching classes ignores mixin

I have extended the ActiveRecord::Base class as follows:
lib/activerecord_ext.rb:
class ActiveRecord::Base
named_scope(
:recent,
:conditions => ['created_at > ?', (Time.new - 3.day)],
:order => 'created_at DESC',
:limit => 5
)
end
In config/environment.rb:
require "activerecord_ext"
This works fine until class caching is enabled. When I set
config.cache_classes = true
I get this error:
>> Person.recent
NoMethodError: You have a nil object when you didn't expect it!
The error occurred while evaluating nil.call
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.3/lib/active_record/named_scope.rb:102:in `recent'
from (irb):1
I assume I'm doing something wrong with the inclusion of the extension. Any help would be greatly appreciated.
Is require 'activerecord_ext' before or after the config.cache_classes = true line? In any case, try putting require 'activerecord_ext' in an initializer instead.

Resources