deserialize database object - ruby-on-rails

I have a statistic model
class Statistic < ActiveRecord::Base
serialize :value
end
The model contains a value attribute containing a Goals object. I want to deserialize the goals object
when I do
goals = Statistic.all
goals.each do |goal|
test = goal.value
end
I get an error
value was supposed to be a Goals, but was a String
In the debugger I see that goal.value is of type String and contains the goal data
--- !ruby/object:Goals \ngoals: {}\n\ngoals_against: 1\ngoals_for: 0\nversion: 1\n
When I add serialize :value, Goals I get following error when deserialzing
ActiveRecord::SerializationTypeMismatch in ClubsController#new
value was supposed to be a Goals, but was a String
The Goals class
class Goals
attr_accessor :goals
attr_accessor :goals_for
attr_accessor :goals_against
attr_accessor :goals_own
attr_accessor :penalty_for
attr_accessor :penalty_against
def initialize(goals = nil, goals_against = nil, goals_own = nil, penalty_for = nil, penalty_against = nil)
#version = 1
if goals.nil?
#goals = {}
else
#goals = goals
end
#goals_against = goals_against.to_i
#goals_own = goals_own.to_i unless goals_own.nil?
unless penalty_for.nil?
#penalty_for = penalty_for.to_i
#penalty_against = penalty_against.to_i
end
set_goals_for()
end
private
def set_goals_for
#goals_for = 0
#goals.each_value {|value| #goals_for += value.to_i }
#goals_for += #goals_own unless #goals_own.nil?
end
end
Someone knows how I can make rails know that its an goals object and not a string?
Thanks

Most of my experience with serialization problems comes from Rails 1 era, but I recall two usual reasons of serialization failures:
silently ignored exceptions
class reloading
Looking at the file ./activerecord/lib/active_record/base.rb (tag v3.0.7 from git) I see that there is a 'rescue' clause:
def object_from_yaml(string)
return string unless string.is_a?(String) && string =~ /^---/
YAML::load(string) rescue string
end
You may try to investigate what exception is thrown by YAML::load. I usually change this method into something like this:
begin
YAML::load(string)
rescue => e
Rails.logger.warn "YAML exception (ignored): #{e.inspect}"
string
end
About the class reloading, is your problem also visible in test mode? I was registering my classes in YAML, and noticed that the class definition was gone in each second request, since the class object was recreated, and the registered one was taken away from the class chains. I don't think this is your problem, but I am signalling it anyway - maybe this will be helpful?

Related

How to cause manually ActiveRecord RecordInvalid in Rails

Entry: I have attribute class that derive from one of the ActiveRecord types and implement method cast(value) which transforms provided value to derived Active Record type. In our case we perform transformation only when the provided value is a String otherwise the default integer casting is performed. Private method to_minutes converts formatted time to an integer representing spent minutes. I assumed that 1d = 8h = 480m. E.g. result of to_minutes('1d 1h 1m') = 541. (I used this resource Custom Attributes in Ruby on Rails 5 ActiveRecord
If the string that came has no numbers, I need to return a validation error, and set this error to the #card.errors. How should I do it? I try:
if time.scan(/\d/).empty?
raise ActiveRecord::RecordInvalid.new(InvalidRecord.new)
end
But it does not work, I get an error
NameError in CardsController#update
uninitialized constant CardDuration::Type::InvalidRecord
Extracted source (around line #15):
I create my own attribute for integer type:
class CardDuration
class Type < ActiveRecord::Type::Value
def cast(value)
if value.is_a?(String)
to_seconds(value)
else
super
end
end
private
def to_seconds(time)
if time.scan(/\d/).empty?
return raise ActiveRecord::RecordInvalid.new, { errors: {message: 'Duration is too short (minimum is 1 number)'} }
end
time_sum = 0
time.split(' ').each do |time_part|
value = time_part.to_i
type = time_part[-1,1]
case type
when 'm'
value
when 'h'
value *= 60
when 'd'
value *= 8*60
else
value
end
time_sum += value
end
time_sum
end
end
end
and inside model:
class Card < ApplicationRecord
validates :duration, length: { within: 0..14880 }
attribute :duration, CardDuration::Type.new
end
Also validation doesn't work, and I do not understand why.
Thanks)
Inside controller this field can only be updated, so I need set the error to the #card.errors:
class CardsController < ApplicationController
def update
if #card.update(card_params)
flash[:success] = "Card was successfully updated."
else
flash[:error] = #card.errors.full_messages.join("\n")
render status: 422
end
rescue ActiveRecord::RecordInvalid => e
return e.record
end
end
in ActiveRecord::RecordInvalid.new(...) you need to pass structure, which have method 'errors' documentation.
try to raise ActiveRecord::RecordInvalid.new(self.new)
or write or own class, with method errors, which will be handle your exeptions
The only problem that I can see in your code is the way you're raising the invalid record error.
The correct way to do that would be-
raise ActiveRecord::RecordInvalid

Rails 5 - iterate until field matches regex

In my app that I am building to learn Rails and Ruby, I have below iteration/loop which is not functioning as it should.
What am I trying to achieve?
I am trying to find the business partner (within only the active once (uses a scope)) where the value of the field business_partner.bank_account is contained in the field self_extracted_data and then set the business partner found as self.sender (self here is a Document).
So once a match is found, I want to end the loop. A case exists where no match is found and sender = nil so a user needs to set it manually.
What happens now, is that on which ever record of the object I save (it is called as a callback before_save), it uses the last identified business partner as sender and the method does not execute again.
Current code:
def set_sender
BusinessPartner.active.where.not(id: self.receiver_id).each do |business_partner|
bp_bank_account = business_partner.bank_account.gsub(/\s+/, '')
rgx = /(?<!\w)(#{Regexp.escape(bp_bank_account)})?(?!\‌​w)/
if self.extracted_data.gsub(/\s+/, '') =~ rgx
self.sender = business_partner
else
self.sender = nil
end
end
end
Thanks for helping me understand how to do this kind of case.
p.s. have the pickaxe book here yet this is so much that some help / guidance would be great. The regex works.
Using feedback from #moveson, this code works:
def match_with_extracted_data?(rgx_to_match)
extracted_data.gsub(/\s+/, '') =~ rgx_to_match
end
def set_sender
self.sender_id = matching_business_partner.try(:id) #unless self.sender.id.present? # Returns nil if no matching_business_partner exists
end
def matching_business_partner
BusinessPartner.active.excluding_receiver(receiver_id).find { |business_partner| sender_matches?(business_partner) }
end
def sender_matches?(business_partner)
rgx_registrations = /(#{Regexp.escape(business_partner.bank_account.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.registration.gsub(/\s+/, ''))})|(#{Regexp.escape(business_partner.vat_id.gsub(/\s+/, ''))})/
match_with_extracted_data?(rgx_registrations)
end
In Ruby you generally want to avoid loops and #each and long, procedural methods in favor of Enumerable iterators like #map, #find, and #select, and short, descriptive methods that each do a single job. Without knowing more about your project I can't be sure exactly what will work, but I think you want something like this:
# /models/document.rb
class Document < ActiveRecord::Base
def set_sender
self.sender = matching_business_partner.try(:id) || BusinessPartner.active.default.id
end
def matching_business_partners
other_business_partners.select { |business_partner| account_matches?(business_partner) }
end
def matching_business_partner
matching_business_partners.first
end
def other_business_partners
BusinessPartner.excluding_receiver_id(receiver_id)
end
def account_matches?(business_partner)
rgx = /(?<!\w)(#{Regexp.escape(business_partner.stripped_bank_account)})?(?!\‌​w)/
data_matches_bank_account?(rgx)
end
def data_matches_bank_account?(rgx)
extracted_data.gsub(/\s+/, '') =~ rgx
end
end
# /models/business_partner.rb
class BusinessPartner < ActiveRecord::Base
scope :excluding_receiver_id, -> (receiver_id) { where.not(id: receiver_id) }
def stripped_bank_account
bank_account.gsub(/\s+/, '')
end
end
Note that I am assigning an integer id, rather than an ActiveRecord object, to self.sender. I think that's what you want.
I didn't try to mess with the database relations here, but it does seem like Document could include a belongs_to :business_partner, which would give you the benefit of Rails methods to help you find one from the other.
EDIT: Added Document#matching_business_partners method and changed Document#set_sender method to return nil if no matching_business_partner exists.
EDIT: Added BusinessPartner.active.default.id as the return value if no matching_business_partner exists.

Transaction unable to Rollback beyond certain Model

I have to process a very long form with multiple models.
def registerCandidate
action_redirect = ""
id = ""
ActiveRecord::Base.transaction do
begin
#entity = Entity.new( name: params[:entity][:name], description: params[:entity][:description], logo: params[:entity][:logo])
#access = Access.new( username: params[:access][:username], password: params[:access][:password], remember_me: params[:access][:rememberme], password_confirmation: params[:access][:password_confirmation])
#access.entity = #entity
#access.save!
#biodata = Biodatum.new(
date_of_birth: params[:biodatum][:birthday],
height: params[:biodatum][:height],
family_members: params[:biodatum][:family_members],
gender: params[:biodatum][:gender],
complexion: params[:biodatum][:complexion],
marital_status: params[:biodatum][:marital_status],
blood_type: params[:biodatum][:blood_type],
religion: params[:biodatum][:religion],
education: params[:biodatum][:education],
career_experience: params[:biodatum][:career_experience],
notable_accomplishments: params[:biodatum][:notable_accomplishments],
emergency_contact: params[:biodatum][:emergency_contact],
languages_spoken: params[:biodatum][:languages_spoken]
)
#biodata.entity = #entity
#biodata.save!
#employee = Employee.new()
#employee.entity = #entity
#employee.save!
action_redirect = "success_candidate_registration"
id = #access.id
#action_redirect = "success_candidate_registration?id=" + #access.id
#Error Processing
rescue StandardError => e
flash[:collective_errors] = "An error of type #{e.class} happened, message is #{e.message}"
action_redirect = "candidate_registration"
end
end
redirect_to action: action_redirect, access_id: id
end
If I raise any error beyond #access.save! it does the entire transaction without rolling back. How do you modify in order for any error related to all models rollback everything?
Since you are rescuing StandardError - which most errors derive from you could very well be swallowing errors which could cause a rollback.
This is commonly known as Pokemon exception handling (Gotta catch em' all) and is an anti-pattern.
Instead listen for more specific errors such as ActiveRecord::RecordInvalid - and raise a raise ActiveRecord::Rollback on error to cause a rollback.
If you haven't already read the Active Record Transactions docs, there is some very good information there about what will cause a rollback.
Also if you want to be a good Ruby citizen and follow the principle of least surprise use snakecase (register_candidate) not camelCase (registerCandidate) when naming methods.
Added
Instead of copying every value from params to your models you should use strong parameters and pass the model a hash.
This reduces code duplication between controller actions and keeps your controllers nice and skinny.
class Biodatum < ActiveRecord::Base
alias_attribute :date_of_birth, :birthday # allows you to take the :birthday param
end
# in your controller:
def register_candidate
# ...
#biodata = Biodatum.new(biodatum_params)
# ...
end
private
def biodatum_params
params.require(:biodatum).allow(
:birthday, :height, :family_members :family_members
) # #todo whitelist the rest of the parameters.
end

Rails variable returns two different values?

For some weird reason an instance variable I have puts out two different values on two different occasions.
$ puts #project.to_yaml
gives:
id: 3
title: '123'
created_at: 2014-04-07 23:54:18.253262000 Z
updated_at: 2014-04-09 09:20:33.847246000 Z
amount_donated: 50000
and
$ #project.amount_donated
gives:
nil
Explain this one to me because I'm terribly lost.
EDIT
Project model
class Project < ActiveRecord::Base
require 'date'
attr_accessor(:amount_donated)
before_save :convert_params
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
update_column(:amount_donated, value.to_i) shows that you have a column amount_donated, but attr_accessor :amount_donated shows that you have a virtual attribute. So which one is it?
I'd suggest removing attr_accessor :amount_donated
edit:
The attr_accessor :amount_donated does something like this:
class Project < ActiveRecord::Base
require 'date'
before_save :convert_params
def amound_donated
#amount_donated
end
def amound_donated=(value)
#amount_donated = value
end
def convert_params
if amount_donated.present?
value = amount_donated.to_s.split(',').join
value = value.to_f * 100
update_column(:amount_donated, value.to_i)
end
end
end
Thus when you accessed #project.amount_donated you were actually accessing the getter method amount_donated not the column (ActiveRecord getter).
Seems that to_yaml saw the column instead of the ActiveRecord's getter.
Try this, might be you are using cached copy of #project
#project.reload.amount_donated

How to format values before saving to database in rails 3

I have a User model with Profit field. Profit field is a DECIMAL (11,0) type. I have a masked input on the form which allows user to input something like $1,000. I want to format that value and remove everything except numbers from it so i will have 1000 saved. Here is what i have so far:
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
end
But it keeps saving 0 in database. Looks like it is converting it to decimal before my formatting function.
Try this:
def profit=(new_profit)
self[:profit] = new_profit.gsub(/[^0-9]/, '')
end
First of all, this:
def format_values
self.profit.to_s.delete!('^0-9') unless self.profit.nil?
end
is pretty much the same as this:
def format_values
return if(self.profit.nil?)
p = self.profit
s = p.to_s
s.delete!('^0-9')
end
So there's no reason to expect your format_values method to have any effect whatsoever on self.profit.
You could of course change format_values to assign the processed string to self.profit but that won't help because your cleansing logic is in the wrong place and it will be executed after '$1,000' has been turned into a zero.
When you assign a value to a property, ActiveRecord will apply some type conversions along the way. What happens when you try to convert '$1,000' to a number? You get zero of course. You can watch this happening if you play around in the console:
> a = M.find(id)
> puts a.some_number
11
> a.some_number = 'pancakes'
=> "pancakes"
> puts a.some_number
0
> a.some_number = '$1,000'
=> "1,000"
> puts a.some_number
0
> a.some_number = '1000'
=> "1000"
> puts a.some_number
1000
So, your data cleanup has to take place before the data goes into the model instance because as soon as AR gets its hands on the value, your '$1,000' will become 0 and all is lost. I'd put the logic in the controller, the controller's job is to mediate between the outside world and the models and data formatting and mangling certainly counts as mediation. So you could have something like this in your controller:
def some_controller
fix_numbers_in(:profit)
# assign from params as usual...
end
private
def fix_numbers_in(*which)
which.select { |p| params.has_key?(p) }.each do |p|
params[p] = params[p].gsub(/\D/, '') # Or whatever works for you
end
end
Then everything would be clean before ActiveRecord gets its grubby little hands on your data and makes a mess of things.
You could do similar things by overriding the profit= method in your model but that's really not the model's job.
class User < ActiveRecord::Base
before_save :format_values
private
def format_values
self.profit = profit.to_s.gsub(/\D/,'') if profit
end
end
def format_values
self.profit.to_d!
end
I recommend you to write custom setter for this particular instance variable #profit:
class User
attr_accessor :profit
def profit= value
#profit = value.gsub(/\D/,'')
end
end
u = User.new
u.profit = "$1,000"
p u.profit # => "1000"
I would suggest using the rails helper of number with precision. Below is some code.
Generic Example:
number_with_precision(111.2345, :precision => 1, :significant => true) # => 100
Rails code Example:
def profit=(new_profit)
number_with_precision(self[:profit], :precision => 1, :significant => true)
end

Resources