I have an object that is apparently randomly getting set to an id of 1 and the source of where this is happening is unknown in the codebase. Could be on an update attributes for a user for which school is associated.
How can I raise an error or otherwise log when this happens so I can track it down and resolve it?
Below is a first attempt but doesn't seem to take into account update_attributes
belongs_to :foo_school, :foreign_key => 'school_id'
def foo_school=(foo_school)
begin
raise "Found" if foo_school == FooSchool.find(1)
rescue Exception => e
# Track it down through the stack trace
Rails.logger.error e
ensure
write_attribute(:foo_school, foo_school)
end
end
An observer can do this for you. What you do with this observer is up to you.
$ rails g observer Oddity
Add this in config/application.rb (search for "observer" in that file, there's an example by default).
config.active_record.observers = :oddity_observer
class OddityObserver < ActiveRecord::Observer
observe :foo
def after_update(record)
raise "Boom" if record.id == 1
end
end
Related
I am using gem roo to import CSV data. It works smoothly, until the point where there is an association, and am hoping that roo can translate the string into the corresponding integer value in the association. In my case, I have a Staff model which belongs to State.
class State < ApplicationRecord
has_many :staffs
end
class Staff < ApplicationRecord
belongs_to :state
end
This means that I have state_id column in the staffs table. In my CSV, however, the end user has the names of the states, which correspond to the ones in the states tables. When I try to import the CSV, I get the error:
ActiveRecord::AssociationTypeMismatch in StaffsImportsController#create
State(#134576500) expected, got "Texas" which is an instance of String(#20512180)
The highlighted source is:
staff.attributes = row.to_hash
Is it possible for gem roo to translate 'Texas' in the csv file to, say, id 2, instead of the end user doing a lot of translation work before uploading the data?
Here is staffs_imports.rb
class StaffsImport
include ActiveModel::Model
require 'roo'
attr_accessor :file
def initialize(attributes={})
attributes.each { |name, value| send("#{name}=", value) }
end
def persisted?
false
end
def open_spreadsheet
case File.extname(file.original_filename)
when ".csv" then Csv.new(file.path, nil, :ignore)
when ".xls" then Roo::Excel.new(file.path, nil, :ignore)
when ".xlsx" then Roo::Excelx.new(file.path)
else raise "Unknown file type: #{file.original_filename}"
end
end
def load_imported_staffs
spreadsheet = open_spreadsheet
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
staff = Staff.find_by_national_id(row["national_id"]) || Staff.new
staff.attributes = row.to_hash
staff
end
end
def imported_staffs
#imported_staffs ||= load_imported_staffs
end
def save
if imported_staffs.map(&:valid?).all?
imported_staffs.each(&:save!)
true
else
imported_staffs.each_with_index do |staff, index|
staff.errors.full_messages.each do |msg|
errors.add :base, "Row #{index + 6}: #{msg}"
end
end
false
end
end
end
And finally the staff_imports_controller.rb:
class StaffsImportsController < ApplicationController
def new
#staffs_import = StaffsImport.new
end
def create
#staffs_import = StaffsImport.new(params[:staffs_import])
if #staffs_import.save
flash[:success] = "You have successfully uploaded your staff!"
redirect_to staffs_path
else
render :new
end
end
end
Any help/clues will be highly appreciated.
I managed to get a solution to this, thanks to a wonderfully detailed question and great answer provided here Importing CSV data into Rails app, using something other then the association "id"
I have an issue where a binary representaiton of an IPv6 address will cause the sql_color in active record logs to generate a very long error message.
The query does work and does return expected results.
I think this is because it the binary IPv6 looks like:
"\xFE\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\f"
And the sql_color method is interpreting that as control codes.
If I set:
Rails.applicaiton.config.colorize_logging = false
It still does it even though there is no longer any color being shown.
Ideally I would like to know the best way to bypass sql_color.
Right now I have just overridden the method and put it in a begin/rescue block.
How to reproduce:
rails new binary_bug -d mysql
cd binary_bug
rails db:create
rails g model Bug name:text first_ip:binary second_ip:binary
Update the migration to
class CreateBugs < ActiveRecord::Migration[5.2]
def change
create_table :bugs do |t|
t.text :name
t.binary :first_ip, limit: 16
t.binary :second_ip, limit: 16
t.timestamps
end
end
end
rails db:migrate
Bug.create(name: 'test1', first_ip: IPAddr.new('fe80::c').hton, second_ip: IPAddr.new('fe80::c').hton.to_s )
Bug.create(name: 'test2', first_ip: IPAddr.new('2001:db8:1234::').hton, second_ip: IPAddr.new('2001:db8:1234:ffff:ffff:ffff:ffff:ffff').hton.to_s )
# Try to search the DB.
bugs = Bug.where("first_ip > ?", IPAddr.new('2001:db8:1234::12').hton)
This will give a very long error that starts with:
Could not log "sql.active_record" event. ArgumentError: invalid byte sequence in UTF-8
The first file it points towards is:
gems/activerecord-5.2.2/lib/active_record/log_subscriber.rb:71:in `sql_color'"
Which is a private method and looks like this:
def sql_color(sql)
case sql
when /\A\s*rollback/mi
RED
when /select .*for update/mi, /\A\s*lock/mi
WHITE
when /\A\s*select/i
BLUE
when /\A\s*insert/i
GREEN
when /\A\s*update/i
YELLOW
when /\A\s*delete/i
RED
when /transaction\s*\Z/i
CYAN
else
MAGENTA
end
end
If I replace that with just MAGENTA or wrap it in a begin/rescue block and restart spring it will work fine.
Consider the following setup:
class Task
has_many :users, through: :task_users
end
class User
has_many :tasks, through: :tasks_users
end
class TaskUser
# Joins table
has_many :comments, dependent: :destroy
end
class Comment
belongs_to :task_user
end
Now if I perform a standard #destroy command, such as:
tu = TaskUser.first
tu.destroy
Then all comments associated to the task-user will also be destroyed.
However, suppose you want to update a user's tasks via #collection_singular_ids=, like so:
u = User.first
puts u.task_ids # => [1, 2, 3]
u.task_ids = [1, 2]
Doing this (without even calling #save explicitly!) will trigger SQL like:
(0.3ms) BEGIN
SQL (0.4ms) DELETE FROM `task_users` WHERE `task_users`.`task_id` = 3 AND `task_users`.`user_id` = 1
(2.0ms) COMMIT
...So the associated Comments get orphaned.
The same issue occurs if you use #attributes=:
u.attributes = { task_ids: [1, 2] }
Is there a clean way to ensure that the associated Comments will always be destroyed (i.e. never orphaned)?
Thanks to #engineersmnky for pointing me in the right direction.
This may not be the prettiest solution, but a viable option is to define a callback on the association, such as:
class User
has_many :tasks,
through: :tasks_users,
before_remove: ->(user, task) do
task_user = TaskUser.find_by!(user: user, task: task)
task_user.comments.each(&:destroy!)
end
# Or alternatively, this can be defined as a method:
has_many :tasks,
through: :tasks_users,
before_remove: :destroy_task_user_comments
private
def destroy_task_user_comments(task)
task_user = TaskUser.find_by!(user: self, task: task)
task_user.comments.each(&:destroy!)
end
end
Note that I have used bang (!) methods within the block, so that the entire transaction will rollback if the an exception is raised - as per the documentation:
if any of the before_remove callbacks throw an exception, the object will not be removed from the collection.
Datamapper isn't saving my user models.
(This is a Sinatra webapp and the db is an AWS RDS mysql db.)
The User model:
class User
include DataMapper::Resource
property :uid, Serial
property :user, String, :key => true, :length => 3..20
property :pass, String, :required => true, :length => 6..50
end
The code to set it:
post "/register" do
username = params["username"]
password = params["password"]
begin
encrypted_password = BCrypt::Password.create password
meme = User.new :user => username, :pass => encrypted_password
meme.save
raise DatabaseError, "User record not saved" unless meme.saved?
flash[:register] = "Welcome, new user! Please log in now."
redirect "/login"
# disabled rescue stuff...
end
end
(if you want, test it yourself at dittoslash.uk)
(can i do this on stack overflow? edit this out if you can't)
EDIT: Updated validation rules. Now I'm getting an error of 'Pass must be between 6 and 50 characters long' (with a 28 (or 30?) character password)
max pleaner answered this for me.
For Googlers looking for answers:
check your validations, and make sure that encryption dosen't make the password longer than your maximum.
I have my Report model:
class Report < ActiveRecord::Base
belongs_to :user
attr_accessible :ready_status, :document
mount_uploader :document, DocumentUploader
def attach( report_file )
self.update_attributes( :document => File.open( report_file ), :ready_status => true )
end
end
This model has attach metod, which i use to save document and other param. Now i want to test that this function works.
/spec/models/report_spec.rb
# encoding: utf-8
require 'spec_helper'
describe Report do
before(:each) do
#user = User.make!
end
...
context "File's saving" do
before(:each) do
#report = #user.reports.create
#csv_report_file = "#{Rails.root}/spec/files/report.csv"
end
it "CSV should be saved" do
csv_report_filename = #csv_report_file.split("/").last
#report.attach #csv_report_file
#report.reload
#report.document.file.filename.should == csv_report_filename
end
end
end
When i try to saving file from /spec/files i get such error:
Report File's saving CSV should be saved
Failure/Error: #report.document.file.filename.should == csv_report_filename
NoMethodError:
undefined method `filename' for nil:NilClass
But when i try another file from another folder (for example "#{Rails.root}/samples/my-report.csv") then my test passes.
How can i fix that?
Oh, i found the answer. Carrierwave doesn't save empty file and i had one. When i added some data in the file (/spec/files/report.csv) my problem has gone.