I have a User model which has_one :address, as: :addressable. Here address is a polymorphic association.
When saving the user, if it errors out. Because of failing validations, Errors are not grouped for address
The output of user.errors.to_json is
"{\"address.city\":[\"can't be blank\"],\"address.state\":[\"can't be blank\"],\"address.country\":[\"can't be blank\"],\"address.zipcode\":[\"can't be blank\",\"is invalid\"],\"address.business_phone\":[\"is invalid\"],\"name\":[\"can't be blank\"]}"
I would like the output to be
"address: {city: ["can't be blank], state: ["can't be blank]..}"
Is it possible to group the error messages or exclude the error messages of the association?
Because user.address.errors seem to give what I need, but I need to get the user model errors without the errors of address model.
A working solution
# Note: This modifies the given Hash
def extract_address_errors_from_user_errors!(user_errors_hash)
address_error_prefix = 'address.'
empty_str = ''
address_error_keys = []
address = Address.new
user_errors_hash.each do |k, v|
unless k.start_with?(address_error_prefix)
next
end
# Collect the address error keys to later remove them from user errors hash
address_error_keys << k
address_attr_name = k.gsub(address_error_prefix, empty_str)
address_attr_name_sym = address_attr_name.to_sym
address.errors.add(address_attr_name_sym, v)
address.errors[address_attr_name_sym].flatten!
end
# Remove the address errors from user errors
user_errors_hash.except!(*address_error_keys)
address.errors.to_hash
end
Usage
user_errors_hash = user.errors.to_hash
address_errors_hash = extract_address_errors_from_user_errors!(user_errors_hash)
user_errors_hash.merge!(address: address_errors_hash)
# This should give you the desired output
user_errors_hash.to_json
Related
I would like to store each value from an array.
For example the form sends me this data:
"attendance"=>{"event_id"=>"6", "member_id"=>["16", "28", "26"]}
I'd like the database to store the data as:
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "16")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "28")
INSERT INTO "attendances" ("event_id", "member_id") VALUES ("6", "26")
I've tried to use the usual way of inserting data in Rails, but it failed because the member_ids didn't get passed (I've tried to print the member_ids after the Attendance.new(attendance_params)):
def create
#attendance = Attendance.new(attendance_params)
# puts #attendance[:event_id]
# puts #attendance[:member_id] -> Nothing showed up here.
if #attendance.save
flash[:success] = "Successfully created"
redirect_to new_attendance_path
else
#error_msg = #attendance.errors.full_messages
flash[:error] = #error_msg # Prints ["Member must exist"]
redirect_to new_attendance_path
end
end
I've also tried creating a new function in the model to change the Attendance.new but it'll return
NoMethodError - undefined method `new_each' for #<Class:0x000000000d1e7280>: app/controllers/attendances_controller.rb:17:in `create'
This is my current model:
class Attendance < ApplicationRecord
belongs_to :event
belongs_to :member
# def new_each(attendance)
# attendance_event = attenance[:event_id]
# attendance_members = attendance[:member_id]
# I tried to iterate and save each data here.
# end
end
So, how do I save each value from an array input (from the form) and save it to database?
Any answers and comment will be very appreciated.
You are trying to add attendance in bulk, For that you can do 2 things,
use gem bulk_insert, it's very easy to use here is the link https://github.com/jamis/bulk_insert
you need to iterate through all the members and events from params and create a record for each. though it's a very dirty way.
sample code would be like this. you can modify it as per your need.
errors = {}
attendance_params[:member_id].each do |member_id|
attendence = Attendence.new(event_id:attendance_params[:event_id], member_id: member_id)
errors[member_id] = "Could not save attendance, error =
attendance.errors.full_messages" unless
attendence.save
end
I have a rails app. I have a table User and a column Number which is a string. Some users saved their phone number with spaces (for example 1234 1234) and now I want to remove the space from their phone numbers.
I tried this but it didn't work:
space = " "
phones = User.where("number like ?", "%#{space}%").pluck(:number)
phones.each do |phone|
phone = phone.gsub(/\s+/, "")
phone.save
end
I got the error NoMethodError: undefined method 'save' How can I do this properly?
You need to have the user object to save it. Read inline comments below
space = " "
users = User.where("number like ?", "%#{space}%") # collect users with number having space character here.
# then iterate on those users
users.each do |user|
user.number = user.number.gsub(/\s+/, "") # notice here, changing the phone number of that user
user.save # and saving that user with the updated `number`
end
You pluck data from User table. Thus, phones variable contains a number array not USER objects. You can't use save on a array element. That's why the error occurs.
You can do the following:
space = " "
phones = User.where("number like ?", "%#{space}%")
phones.each do |phone|
phone.number = phone.number.gsub(/\s+/, "")
phone.save
end
The way you could do is create a rake task to update the existing records on the system.
namespace :update do
desc 'Strip space from existing numbers from Users'
task(:number => ::environment) do
space = ' '
numbers_with_space = User.where("number like ?", "%#{space}%")
numbers_with_space.each do |a|
a.number = a.number.gsub!(/\s+/, '')
a.save(validate: false) # You would like to use
# validate false in order
# to stop other validation from updating the record.
end
end
Then execute the rake task.
bundle exec rake update:number
Another way to handle this beforehand can be through reformatting number during validation. This way you'll not need to run the rake task or code to reformat and save when new data are entered in app.
class User < ApplicationRecord
before_validation :reformat_number, on: [:create, :update]
private
def reformat_number
self.number.gsub!(/\s+/, '')
end
end
I defined my own custom field type Price for my application. I defined all the required methods (demongoize, mongoize, ..) and it works perfectly fine retrieving the data from mongodb, sending it to front end and getting data back and store them in mongo db. My Price class stores the data as an array [value, currency] in the DB and also my front end expect the price to be delivered as a an array. All ok so far.
In my model I now added a Hash which reflects a tree structure containing objects which have a price. I was re-using the price class for these prices when creating the tree structure.
But now I get an undefined bs_type error when I try to save this hash field to the database. Looks like rails/mongoid doesnt find a method to transform the price in the Hash into a suitable mongoid format.
I tried to define as_json, to_json, to_s, ... everything on my Price model but I always get the same message: undefined bson_type for Price whenever I want to save my tree field.
(If I store my values as [value,currency] in the hash - not using Price, obviously all works fine)
Any idea?
class Price
def initialize(value = 0, currency = '')
#value,#currency = value, currency
end
def mongoize
[#value,#currency.to_s]
end
class << self
def demongoize(object)
if object
cur = object[1] || ''
Price.new(object[0], cur)
else
Price.new()
end
end
def mongoize(object)
case object
when Price then object.mongoize
when Array then Price.new(object[0], object[1]).mongoize
else object
end
end
def evolve(object)
case object
when Price then object.mongoize
else object
end
end
end
I have another model:
class Financial
include Mongoid::Document
field: quote, type: Price
field: tree, type: Hash
end
In my application code setting the quote and saving it works.
Here is the problem:
revenue = Price.new(10,'USD')
record = Financial.new()
record.quote = revenue
record.save # All works so far
record.tree = { data: [10,'USD']}
record.save # this works too
record.tree = { data: revenue}
record.save # here I get the undefined bson_type for Price error.
I'm currently having trouble finding a nice way to code the following situation:
There is a Model called TcpService, which has two attributes, port_from and port_to, both Integers. It also has a virtual attribute called portrange, which is a String. portrange is the String representation of the attributes port_from and port_to, so portrange = "80 90" should yield port_from = 80, port_to = 90. What I'm trying to do now is using the same Formtastic form for creating AND updating a TcpService-object. The form looks pretty standard (HAML code):
= semantic_form_for #tcp_service do |f|
= f.inputs do
= f.input :portrange, as: :string, label: "Portrange"
-# calls #tcp_service.portrange to determine the shown value
= f.actions do
= f.action :submit, label: "Save"
The thing is, I don't know of a non-messy way to make the values I want appear in the form. On new I want the field to be empty, if create failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange. On edit I want the String representation of port_from and port_to to appear, if update failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange.
The Model looks like this, which seems quite messy to me.
Is there a better way of making it achieve what I need?
class TcpService < ActiveRecord::Base
# port_from, port_to: integer
attr_accessor :portrange
validate :portrange_to_ports # populates `port_from` and `port_to`
# using `portrange` AND adds errors
# raises exception if conversion fails
def self.string_to_ports(string)
... # do stuff
return port_from, port_to
end
# returns string representation of ports without touching self
def ports_to_string
... # do stuff
return string_representation
end
# is called every time portrange is set, namely during 'create' and 'update'
def portrange=(val)
return if val.nil?
#portrange = val
begin
self.port_from, self.port_to = TcpService.string_to_ports(val)
# catches conversion errors and makes errors of them
rescue StandardError => e
self.errors.add(:portrange, e.to_s())
end
end
# is called every time the form is rendered
def portrange
# if record is freshly loaded from DB, this is true
if self.port_from && self.port_to && #portrange.nil?
self.ports_to_string()
else
#portrange
end
end
private
# calls 'portrange=(val)' in order to add errors during validation
def portrange_to_ports
self.portrange = self.portrange
end
end
Thanks for reading
In your model
def portrange
return "" if self.port_from.nil? || self.port_to.nil?
"#{self.port_from} #{self.port_to}"
end
def portrange=(str)
return false unless str.match /^[0-9]{1,5}\ [0-9]{1,5}/
self.port_from = str.split(" ").first
self.port_to = str.split(" ").last
self.portrange
end
Using this you should be able tu use the portrange setter and getter in your form.
I have 2 models. User and Want. A User has_many: Wants.
The Want model has a single property besides user_id, that's name.
I have written a custom validation in the Want model so that a user cannot submit to create 2 wants with the same name:
validate :existing_want
private
def existing_want
return unless errors.blank?
errors.add(:existing_want, "you already want that") if user.already_wants? name
end
The already_wants? method is in the User model:
def already_wants? want_name
does_want_already = false
self.wants.each { |w| does_want_already = true if w.name == want_name }
does_want_already
end
The validation specs pass in my model tests, but my feature tests fail when i try and submit a duplicate to the create action in the WantsController:
def create
#want = current_user.wants.build(params[:want])
if #want.save
flash[:success] = "success!"
redirect_to user_account_path current_user.username
else
flash[:validation] = #want.errors
redirect_to user_account_path current_user.username
end
end
The error I get: can't dump hash with default proc
No stack trace that leads to my code.
I have narrowed the issue down to this line:
self.wants.each { |w| does_want_already = true if w.name == want_name }
if I just return true regardless the error shows in my view as I would like.
I don't understand? What's wrong? and why is it so cryptic?
Thanks.
Without a stack trace (does it lead anywhere, or does it just not appear?) it is difficult to know what exactly is happening, but here's how you can reproduce this error in a clean environment:
# initialize a new hash using a block, so it has a default proc
h = Hash.new {|h,k| h[k] = k }
# attempt to serialize it:
Marshal.dump(h)
#=> TypeError: can't dump hash with default proc
Ruby can't serialize procs, so it wouldn't be able to properly reconstitute that serialized hash, hence the error.
If you're reasonably sure that line is the source of your trouble, try refactoring it to see if that solves the problem.
def already_wants? want_name
wants.any? {|want| want_name == want.name }
end
or
def already_wants? want_name
wants.where(name: want_name).count > 0
end