Rails ActiveModelSerialzer include with transform_key - ruby-on-rails

I have ZoneSerializer
class Api::V1::ZoneSerializer < ActiveModel::Serializer
attributes :id, :name, :address, :description, :groupname, :key, :speed_limit
has_many :radius_user_groups
end
And I set transform_key
ActiveModel::Serializer.config.key_transform = :camel_lower
When I get response from server I get radiusUserGroups which is ok. But frontend sends also radiusUserGroups for includes, but there seems to be no autotransform feature for backend. Should I do this manually?

Add in your Application controller This method convert all params to snake_case :
before_action :deep_snake_case_params!
def deep_snake_case_params!(val = params)
case val
when Array
val.map {|v| deep_snake_case_params! v }
when Hash
val.keys.each do |k, v = val[k]|
val.delete k
val[k.underscore] = deep_snake_case_params!(v)
end
val
else
val
end
end

Related

ActiveModelSerializer - serialize array object

I have problems with ActiveModelSerializer.
Model test.rb:
class Test < ActiveModel::Serializer
attr_accessor :id, :userName, :lista, :test2
def initialize params = {}
#lista = Array.new
#test2 = Array.new
params.each { |key, value| send "#{key}=", value }
end
end
Model test2.rb:
class Test2 < ActiveModel::Serializer
attr_accessor :id, :userName
def initialize params = {}
params.each { |key, value| send "#{key}=", value }
end
end
test_serializer.rb:
class TestSerializer < ActiveModel::Serializer
attributes :id, :userName, :lista
attributes :test2
public
def test2
Test2Serializer.new(object.test2).as_json
end
end
test2_serializer.rb:
class Test2Serializer < ActiveModel::Serializer
attributes :id, :userName
end
I get an error:
NoMethodError (undefined methodread_attribute_for_serialization’ for #Array:0x00007f1bb52da460):
After debuggin the code I found out that the Exception is from this line:
object.read_attribute_for_serialization(attr)
In variable object is an array. I think it should be object. So I tried to change
Test2Serializer.new(object.test2).as_json
oto
json: object.test2, each_serializer: Test2Serializer
or:
object.test2, each_serializer: Test2Serializer
That didn't work. Can anyone help me?

Before Validation loop through self attributes for modification

I have created a simple before_validation:
before_validation :strip_tabs
def strip_tabs
end
In my class I want to loop through all my attributes and remove tabs from each value. Most posts I found on SO are people who want to set 1 attribute. But I want to edit all my values.
Question:
How can I loop all self attributes of a model and edit them.
Friend suggested this, but content_column_names does not exist:
self.content_column_names.each {|n| self[n] = self[n].squish}
UPDATE 1: More code:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
attr_accessor \
:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang
validates :e_mail, :email => {:strict_mode => true}
validate :validate_telephone_number
validate :age_is_min_17?
before_validation :strip_tabs
def strip_tabs
binding.remote_pry
end
def age_is_min_17?
birthdate_visible = PersonalField.not_hidden.find_by_name "BIRTHDATE"
if birthdate_visible && birthdate && birthdate > (Date.current - 17.years)
#errors.add(:birthdate, I18n.t("apply.errors.birthdate"))
end
end
def validate_telephone_number
telephone_visible = PersonalField.not_hidden.find_by_name "TELEPHONE"
telephone_current = telephone.dup
if telephone_visible && telephone_current && !telephone_current.empty?
if telephone_current[0] == '+' || telephone_current[0] == '0'
telephone_current[0] = ''
#errors.add(:telephone, I18n.t("apply.errors.telephone")) if !telephone_current.is_number?
else
#errors.add(:telephone, I18n.t("apply.errors.telephone"))
end
end
end
def initialize(hash)
simple_attributes = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
simple_attributes.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end
Update 2: Rails Version:
I'm using Rails '3.2.17'
You can do as following:
before_validation :strip_tabs
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.squish.presence
end
end
But I think that .squish will not work on created_at, updated_at, id, ... Because they are not String!
def strip_tabs
self.attributes.map do |column, value|
self[column] = value.kind_of?(String) ? value.squish.presence : value
end
end
Since your class is not a Rails model (ActiveRecord::Base), you can do as following:
def strip_tabs
self.instance_variables.map do |attr|
value = self.instance_variable_get(attr)
value = value.squish if value.kind_of?(String)
self.instance_variable_set(attr, value)
end
end
This should work
def strip_tabs
self.attributes.each do |attr_name, attr_value|
modified_value = ... # calculate your modified value here
self.write_attribute attr_name, modified_value
end
end
Because it's not an ActiveRecord model you won't have attributes or column_names, but you already have an array of your attribute names in your initialize function. I would suggest making that into a constant so you can access it throughout the model:
class PersonalInfo
include ActiveModel::Validations
include ActiveModel::Validations::Callbacks
extend ActiveModel::Translation
extend ActiveModel::Callbacks
include Sappable
require 'ext/string'
SIMPLE_ATTRIBUTES = [:first_name, :middle_name, :last_name,:birthdate,:sex,
:telephone,:street,:house_number,:city,:postal_code,:country,
:e_mail, :nationality, :salutation, :com_lang]
attr_accessor *SIMPLE_ATTRIBUTES
before_validation :strip_tabs
def strip_tabs
SIMPLE_ATTRIBUTES.each{ |attr| self[attr] = self[attr].squish }
end
...
def initialize(hash)
SIMPLE_ATTRIBUTES.each do |attr|
set_attr_from_json(attr, hash)
end
set_attr_from_json(:birthdate, hash) {|date| Date.parse(date) rescue nil}
end
end

How can I lessen the verbosity of my populate method?

I wrote a form object to populate an Order, Billing, and Shipping Address objects. The populate method looks pretty verbose. Since the form fields don't correspond to Address attributes directly, I'm forced to manually assign them. For example:
shipping_address.name = params[:shipping_name]
billing_address.name = params[:billing_name]
Here's the object. Note that I snipped most address fields and validations, and some other code, for brevity. But this should give you an idea. Take note of the populate method:
class OrderForm
attr_accessor :params
delegate :email, :bill_to_shipping_address, to: :order
delegate :name, :street, to: :shipping_address, prefix: :shipping
delegate :name, :street, to: :billing_address, prefix: :billing
validates :shipping_name, presence: true
validates :billing_name, presence: true, unless: -> { bill_to_shipping_address }
def initialize(item, params = nil, customer = nil)
#item, #params, #customer = item, params, customer
end
def submit
populate
# snip
end
def order
#order ||= #item.build_order do |order|
order.customer = #customer if #customer
end
end
def shipping_address
#shipping_address ||= order.build_shipping_address
end
def billing_address
#billing_address ||= order.build_billing_address
end
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
if order.bill_to_shipping_address?
billing_address.name = params[:shipping_name]
billing_address.street = params[:shipping_street]
# Repeat for city, state, post, code, etc...
else
billing_address.name = params[:billing_name]
billing_address.street = params[:billing_street]
# Repeat for city, state, post, code, etc...
end
end
end
Here's the controller code:
def new
#order_form = OrderForm.new(#item)
end
def create
#order_form = OrderForm.new(#item, params[:order], current_user)
if #order_form.submit
# handle payment
else
render 'new'
end
end
Noe I am not interested in accepts_nested_attributes_for, which presents several problems, hence why I wrote the form object.
def populate
order.email = params[:email]
shipping_params = %i[shipping_name shipping_street]
billing_params = order.bill_to_shipping_address? ?
shipping_params : %i[billing_name billing_street]
[[shipping_address, shipping_params], [billing_address, billing_params]]
.each{|a, p|
a.name, a.street = params.at(*p)
}
end
How about
class Order < ActiveRecord::Base
has_one :shipping_address, class_name: 'Address'
has_one :billing_address, class_name: 'Address'
accepts_nested_attributes_for :shipping_address, :billing_address
before_save :clone_shipping_address_into_billing_address, if: [check if billing address is blank]
Then when you set up the form, you can have fields_for the two Address objects, and side step the populate method entirely.
A possible fix would be to use a variable for retrieving those matching params, like so:
def populate
order.email = params[:email]
shipping_address.name = params[:shipping_name]
shipping_address.street = params[:shipping_street]
# etc...
#set a default state
shipping_or_billing = "shipping_"
#or use a ternary here...
shipping_or_billing = "billing_" if order.bill_to_shipping_address?
billing_address.name = params["shipping_or_billing" + "name"]
billing_address.street = params["shipping_or_billing" + "street"]
...
end
Your address classes should probably have a method that would set the values for all the address properties from a hash that it would receive as an argument.
That way your populate method would only check for order.bill_to_shipping_address? and them pass the correct dictionary to the method I'm suggesting.
That method on the other hand, would just assign the values from the hash to the correct properties, without the need for a conditional check.

Is possible to use Rails ActiveModel::Validations per instance?

theres an excerpt of my code:
module Configuracao
extend self
class Key
include ActiveModel::Validations
attr_accessor :name, :type, :default, :validations, :group, :available_values
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
#validations = params[:validations]
#in this way each validation is being added for all keys
Configuracao::Key.class_eval do
validates :value, params[:validations]
end
end
end
end
so for every instance key i will have a diferent validation passed in a hash, example:
Key.new( validations: { presence: true, numericality: true } )
Key.new( validations: { length: { maximum: 30 } } )
There's a way to do it?
well i found a solution, maybe not so elegant or best way to do, but it works
def initialize(params)
params.symbolize_keys!.assert_valid_keys(:name, :type, :default, :validations, :group, :available_values)
#group = params[:group]
#name = params[:name]
#type = params[:type]
#available_values = params[:available_values]
#default = params[:default]
##current_validations = nil
##current_validations = #validations = params[:validations]
class << self
validates :value, ##current_validations unless ##current_validations.blank?
end
end
now each time i instantiate a Key, the class will be modified only for that instance
Will this work?
...
validates :all_hash_validations_pass
...
def all_hash_validations_pass
...iterate through the hash here, and validate each of them
end
If not, you should be able to use a custom validator for more control.

Bulk insert using one model

I'm trying to create a form using textarea and a submit button that will allow users to do bulk insert. For example, the input would look like this:
0001;MR A
0002;MR B
The result would look like this:
mysql> select * from members;
+------+------+------+
| id | no | name |
+------+------+------+
| 1 | 0001 | MR A |
+------+------+------+
| 2 | 0002 | MR B |
+------+------+------+
I'm very new to Rails and I'm not sure on how to proceed with this one. Should I use attr_accessor? How do I handle failed validations in the form view? Is there any example? Thanks in advance.
Update
Based on MissingHandle's comment, I created a Scaffold and replace the Model's code with this:
class MemberBulk < ActiveRecord::Base
attr_accessor :member
def self.columns
#columsn ||= []
end
def self.column(name, sql_type = nil, default = nil, null = true)
columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
end
column :data, :text
validates :data, :create_members, :presence => true
def create_members
rows = self.data.split("\r\n")
#member = Array.new
rows.each_with_index { |row, i|
rows[i] = row.strip
cols = row.split(";")
p = Member.new
p.no = cols[0]
p.name = cols[1]
if p.valid?
member << p
else
p.errors.map { |k, v| errors.add(:data, "\"#{row}\" #{v}") }
end
}
end
def create_or_update
member.each { |p|
p.save
}
end
end
I know the code is far from complete, but I need to know is this the correct way to do it?
class MemberBulk < ActiveRecord::Base
#Tells Rails this is not actually tied to a database table
# or is it self.abstract_class = true
# or #abstract_class = true
# ?
abstract_class = true
# members holds array of members to be saved
# submitted_text is the data submitted in the form for a bulk update
attr_accessor :members, :submitted_text
attr_accessible :submitted_text
before_validation :build_members_from_text
def build_members_from_text
self.members = []
submitted_text.each_line("\r\n") do |member_as_text|
member_as_array = member_as_text.split(";")
self.members << Member.new(:number => member_as_array[0], :name => member_as_array[1])
end
end
def valid?
self.members.all?{ |m| m.valid? }
end
def save
self.members.all?{ |m| m.save }
end
end
class Member < ActiveRecord::Base
validates :number, :presence => true, :numericality => true
validates :name, :presence => true
end
So, in this code, members is an array that is a collection of the individual Member objects. And my thinking is that as much as possible, you want to hand off work to the Member class, as it is the class that will actually be tied to a database table, and on which you can expect standard rails model behavior. In order to accomplish this, I override two methods common to all ActiveRecord models: save and valid. A MemberBulk will only be valid if all it's members are valid and it will only count as saved if all of it's members are saved. You should probably also override the errors method to return the errors of it's underlying members, possibly with an indication of which one it is in the submitted text.
In the end I had to change from using Abstract Class to Active Model (not sure why, but it stoppped working the moment I upgrade to Rails v3.1). Here's the working code:
class MemberBulk
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :input, :data
validates :input, presence: true
def initialize(attributes = {})no
attributes.each do |name, value|
send("#{name}=", value) if respond_to?("#{name}=")
end
end
def persisted?
false
end
def save
unless self.valid?
return false
end
data = Array.new
# Check for spaces
input.strip.split("\r\n").each do |i|
if i.strip.empty?
errors.add(:input, "There shouldn't be any empty lines")
end
no, nama = i.strip.split(";")
if no.nil? or nama.nil?
errors.add(:input, "#{i} doesn't have no or name")
else
no.strip!
nama.strip!
if no.empty? or nama.empty?
errors.add(:input, "#{i} doesn't have no or name")
end
end
p = Member.new(no: no, nama: nama)
if p.valid?
data << p
else
p.errors.full_messages.each do |error|
errors.add(:input, "\"#{i}\": #{error}")
end
end
end # input.strip
if errors.empty?
if data.any?
begin
data.each do |d|
d.save
end
rescue Exception => e
raise ActiveRecord::Rollback
end
else
errors.add(:input, "No data to be processed")
return false
end
else
return false
end
end # def
end

Resources