Rails nested attributes type mispatch error - ruby-on-rails

I am creating a rails api using rails_api gem, I have a model name User and another model named Identity.
The issue I am facing is that whenever I tries to create user from params with nested_attributes it gives me ActiveRecord::AssociationTypeMismatch error
Models
User.rb
class User < ActiveRecord::Base
enum gender: [:male , :female]
has_many :identities ,dependent: :destroy
accepts_nested_attributes_for :identities
has_secure_password
end
Identity.rb
class Identity < ActiveRecord::Base
belongs_to :user
validates_associated :user
end
Controllers
users_controller.rb
class UsersController < ApplicationController
def create
#user = User.new user_params
##user.build.identities(user_params[:identities])
if #user.save
render json: #user , status: :created
else
render json: #user.errors, status: :unprocessable_entity
end
end
private
def user_params
json_params = ActionController::Parameters.new( JSON.parse(request.body.read) )
json_params.require(:user).permit(:first_name, :last_name, :email, :password, :image, :location, :gender,{identities: [:provider, :uid, :url, :token, :expires_at]})
end
end
I am sending the data as json object:
This is the error in server console:
Kindly help me fix this issue. I have tried all the possible solutions. Thanks in advance

Your strong param should be like this:
json_params.require(:user).permit(:first_name, :last_name, :email, :password, :image, :location, :gender,identities_attributes: [:provider, :uid, :url, :token, :expires_at])

json_params.require(:user).permit(:first_name, :last_name, :email, :password, :image, :location, :gender, :identities => [:provider, :uid, :url, :token, :expires_at])
this will resolve your issue as in current way.

Related

Rails: NoMethodError - undefined method `record_type`

I am working on a Rails 6 API only application using
jsonapi-serializer gem.
I am using namespaces for the resources in my application, I also used namespaces for the serializer files.
So my serializer file looks like this:
module Applyportal
class ApplicantSerializer
include JSONAPI::Serializer
attributes :first_name, :middle_name, :last_name, :phone, :email, :username, :password, :nationality, :state_of_origin, :local_government
belongs_to :local_government, serializer: Personalinfo::LocalGovernment
belongs_to :marital_status, serializer: Personalinfo::MaritalStatus
belongs_to :nationality, serializer: Personalinfo::Nationality
has_many :applications, serializer: Applyportal::Application
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end
end
But when I try to make a Post request to this resource I get the error below:
NoMethodError (undefined method `record_type' for #<Class:0x00007f05d4d451f8>
Did you mean? record_timestamps)
I can't seem to figure out what the issue is.
The issue was coming from how I defined my serializer associations
Here's how I solved it:
So I used namespaces for the resources in my application, I also used namespaces for the serializer files.
So my serializer file looked like this:
module Applyportal
class ApplicantSerializer
include JSONAPI::Serializer
attributes :first_name, :middle_name, :last_name, :phone, :email, :username, :password, :nationality, :state_of_origin, :local_government
belongs_to :local_government, serializer: Personalinfo::LocalGovernment
belongs_to :marital_status, serializer: Personalinfo::MaritalStatus
belongs_to :nationality, serializer: Personalinfo::Nationality
has_many :applications, serializer: Applyportal::Application
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end
end
I was missing to add Serializer at the end of each association which was making it difficult for the rails application to find the record_type, so instead of:
belongs_to :local_government, serializer: Personalinfo::LocalGovernment
it should be
belongs_to :local_government, serializer: Personalinfo::LocalGovernmentSerializer
So my serializer file looked like this afterwards:
module Applyportal
class ApplicantSerializer
include JSONAPI::Serializer
attributes :first_name, :middle_name, :last_name, :phone, :email, :username, :password, :nationality, :state_of_origin, :local_government
belongs_to :local_government, serializer: Personalinfo::LocalGovernmentSerializer
belongs_to :marital_status, serializer: Personalinfo::MaritalStatusSerializer
belongs_to :nationality, serializer: Personalinfo::NationalitySerializer
has_many :applications, serializer: Applyportal::ApplicationSerializer
cache_options store: Rails.cache, namespace: 'jsonapi-serializer', expires_in: 1.hour
end
end
That's all.
I hope this helps

Partial validations in multistep forms (Wizard)

I have a multistep form, which I created with wizard. Basically the first tep of the form is user/sign_up - which in my understanding not a step yet. After hitting the sign-up button, user moves to the "real" first step, which is :address.
class UserStepsController < ApplicationController
include Wicked::Wizard
steps :address
def show
#user = current_user || User.from_omniauth(request.env["omniauth.auth"])
render_wizard
end
def update
#user = current_user || User.from_omniauth(request.env["omniauth.auth"])
#user.update!(user_params)
render_wizard #user
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :street, :house_number, :city, :zip_code)
end
def redirect_to_finish_wizard(options = nil, params = nil)
redirect_to new_user_profile_path(current_user)
end
end
This is basically the end of the form already. All gets saved to the user. Now I am stuck with validations.
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable,
:omniauthable, omniauth_providers: %i[facebook]
has_one :profile, dependent: :destroy
after_create :create_profile
accepts_nested_attributes_for :profile
validates :first_name, presence: true
validates :last_name, presence: true
validates :street, presence: true
validates :house_number, presence: true
validates :city, presence: true
validates :zip_code, presence: true
def self.from_omniauth(auth)
where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
user.email = auth.info.email
user.password = Devise.friendly_token[0, 20]
name = auth.info.name
user.first_name = name.split(" ")[0]
user.last_name = name.split(" ")[1]
end
end
end
I would love to work with the the conditional validations in my model and only validate presence if on a certain step. This should be easy, as I theoretically only have one step, which is address. All I find on the internet, is way too complicated. Question is, do I have to somehow change user/sign_up to a first step in the form and address would be the second step? Or is it fine like this? And if so, can I just add the "if" statements to the address attributes in my validations, somehow defining what is the address step? Would it work like this?
def on_address_step?
wizard.steps = wizard.steps.first
end
Or how do I define it? The validations would look like this then:
validates :first_name, presence: true
validates :last_name, presence: true
validates :street, presence: true, if: :on_address_step?
validates :house_number, presence: true, if: :on_address_step?
validates :city, presence: true, if: :on_address_step?
validates :zip_code, presence: true, if: :on_address_step?
This is surely not that easy. For now this also doesn't work. How do I need to change it? Thanks.
P.S: here is also my Users Controller:
class UsersController < ApplicationController
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to user_steps_path
else
render :new
end
end
private
def user_params
params.require(:user).permit(:email, :password, :password_confirmation, :remember_me, :first_name, :last_name, :street, :house_number, :city, :zip_code)
end
end
If filling in the address is a completely separate process I would just branch the address out into its own model and controller.
class User < ApplicationRecord
# ...
has_one :address
end
class Address < ApplicationRecord
# ...
belongs_to :user
validates :first_name, :last_name, :street,
:house_number, :city, :zip_code, presence: true
end
This avoids turning your user model into even more of a god object and removes the need for the conditional validation that makes your model much more aware of the UX steps than it should be.
# routes.rb
resources :addresses, only: [:new, :create]
class UsersController < ApplicationController
# ...
def create
#user = User.new(params[:user])
if #user.save
session[:user_id] = #user.id
redirect_to new_address_path
else
render :new
end
end
end
class AddressesController < ApplicationController
# You should have some sort of method that checks if the user
# is signed in and redirect otherwise
before_action :authenticate_user!
# GET /addresses/new
def new
# I'm assuming you have some sort of method to fetch the signed in user
#address = current_user.build_address
end
# POST /addresses
def create
#address = current_user.build_address(address_params)
if #address.save
redirect_to '/somepath'
else
render :new
end
end
def address_params
params.require(:address).permit(
:first_name, :last_name, :street,
:house_number, :city, :zip_code
)
end
end
<%= form_with(model: #address) %>
# ... inputs
<% end %>
I doubt you really want the complexity involved with using Wicked which is ok if you really need a long multiple step form but in this case there is a far simpler and better design choice.

Persist nested data to database in one transaction

I have 3 models:
class Address < ApplicationRecord
has_one :company_address
end
class CompanyAddress < ApplicationRecord
belongs_to :address, dependent: :destroy
belongs_to :address_type
end
class Company < ApplicationRecord
has_many :company_addresses
end
I am getting JSON data from another application.
The data consists of attributes of a company and one/none or many company_address which consists of only one address each
I want to be able to insert and update the data automatically and if anything fails I want to role the migration back
When I set require on strong_params I don't receive the array of company_addresses, however when I only use permit it works fine
This doesn't work:
params.require(:company)
.permit([
:short, :name, :company_legal_form_id,
:company_role_id, :parent_id, :email,
:fax, :phone, :description,
:comment, :changed_by,
company_addresses: [
:company_id, :address_type_id, :addition,
:comment, :changed_by,
address: [
:street, :zip, :city,
:country_id, :other1, :other2,
:other3, :comment, :changed_by
]
]
])
This works:
params.permit([
:short, :name, :company_legal_form_id,
:company_role_id, :parent_id, :email,
:fax, :phone, :description,
:comment, :changed_by,
company_addresses: [
:company_id, :address_type_id, :addition,
:comment, :changed_by,
address: [
:street, :zip, :city,
:country_id, :other1, :other2,
:other3, :comment, :changed_by
]
]
])
So I created a Form-Object called CompanyForm with these methods.
class CompanyForm
include ActiveModel::Model
attr_accessor(
:company_attributes
)
def save
#company_id = company_attributes.delete('id')
company_addresses_attributes = company_attributes.delete('company_addresses')
company_attributes[:changed_by] = 'user'
company.update!(p company_attributes)
#company_id = company.id
if company_addresses_attributes.empty?
company.company_addresses.destroy_all
end
company_addresses_attributes.each do |company_address_attributes|
#company_address_id = find_company_address_id(company_address_attributes)
address_attributes = company_address_attributes.delete('address')
#address_id = find_address_id(address_attributes)
address_attributes[:changed_by] = 'user'
address.assign_attributes(p address_attributes)
#address_id = address.id
company_address[:changed_by] = 'user'
company_address.build_address(#address.attributes)
company_address.assign_attributes(p company_address_attributes)
company.company_addresses.update!(p company_address.attributes)
end
end
private
def company
#company ||= Company.find_by(id: #company_id) || Company.new()
end
def address
#address ||= Address.find_by(id: #address_id) || Address.new()
end
def company_address
#company_address ||= CompanyAddress.find_by(id: #company_address_id) || CompanyAddress.new()
end
def find_company_id(params)
params.dig(:id)
end
def find_company_address_id(params)
params.dig(:id)
end
def find_address_id(params)
params.dig(:id)
end
end
The first question is: why can't I get company_address as well when I set require on :company?
The second question is, how could I get my code to work without problems? I know that the code is really ugly, however I am new to Rails and Ruby in general.
It looks like an issue with the JSON itself - it would help if you provided actual example of JSON sent in that request. The structure could be different than you expect (eg 'company' nested inside of another key).
Try using binding.pry at the first line of the controller which handles that request and investigate what are returns from params and params.require(:company) it might lead you to the answer.

Rails 4 Devise nested form can't mass-assign protected attributes

I have a devise model that has a nested form (supp_form is the nested object) on sign up. When I submit the form I am getting the following error:
WARNING: Can't mass-assign protected attributes for Business: supp_form_attributes, terms_of_service
app/controllers/businesses/registrations_controller.rb:11:in `create'
I am using the nested_form gem and it seems as if my form is passing field data through to the console. My parameters after submit look like the following:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"XXX", "business"=>{"type"=>"Business", "supp_form_attributes"=>{"title"=>"mr.", "first_name"=>"jane", "last_name"=>"doe", "mobile_phone_number"=>"94034903", "loan_agreement_authorization"=>"1", "work_phone_number"=>"49034903", "business_industry"=>"Natural Resources and Mining", "legal_structure"=>"Sole Proprietorship", "employee_count"=>"5 to 10", "years_in_business"=>"5+ years", "business_address"=>"72 pentland rd", "business_city"=>"Waterdown", "business_postal_code"=>"l0r2h5", "business_province"=>"ON"}
business.rb
class Business < User
# Associations
has_one :supp_form
has_many :loan_applications
has_many :transactions
# Nested attributes
accepts_nested_attributes_for :supp_form, :loan_applications
# After save action
after_save :create_account
# Validations
validates_acceptance_of :terms_of_service
validate :terms_of_service, presence: true
end
supp_form.rb
class SuppForm < ActiveRecord::Base
# Associations
belongs_to :business
# Validations
validates_acceptance_of :terms
validates :business_id, :first_name, :last_name, :work_phone_number, :business_address, :business_postal_code, :business_city, presence: true
end
registraionts_controller.rb
class Businesses::RegistrationsController < Devise::RegistrationsController
before_filter :update_sanitized_params
def new
build_resource({})
resource.build_supp_form
respond_with self.resource
end
def create
super
resource.update_attribute(:railsid, '%010d' % rand(10 ** 10))
end
private
def update_sanitized_params
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, :type, :confirmed_at, :business_name, :terms, :railsid, :terms_of_service,
supp_form_attributes: [:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province])}
end
def after_sign_up_path_for(resource)
business_root_path
end
end
supp_forms_controller.rb
class SuppFormsController < ApplicationController
before_filter :authenticate_user!
def new
#suppform = SuppForm.new(supp_form_params)
end
def create
#suppform = SuppForm.create(supp_form_params)
end
private
def supp_form_params
params.require(:supp_form).permit(:business_id, :title, :loan_agreement_authorization, :first_name,
:last_name, :work_phone_number, :business_address, :business_postal_code,
:business_city, :business_name, :years_in_business, :legal_structure,
:business_industry, :employee_count, :mobile_phone_number, :business_province)
end
end
You are using Rails 4 with strong parameters. And you get an error triggered by the protected_attributes gem (or default rails 3 app).
With strong_parameters on place you can remove safety the protected_attributes gem. And remove the configuration if you have it (config.active_record.whitelist_attributes).

Can't mass-assign protected attributes: even if attr_accessible already added

Can't mass assign :title, :url and :about even if attr_accessible attributes already added. It's fine on rails console but not on online form.
Post Model:
class Post < ActiveRecord::Base
attr_accessible :about, :downv, :names, :points, :title, :upv, :url, :user_id
belongs_to :user
end
User Model:
class User < ActiveRecord::Base
attr_accessible :email, :password_digest, :post_id, :password, :password_confirmation, :name
has_many :posts
has_secure_password
validates_presence_of :password, :on => :create
end
Post Controller create:
def create
#post = User.new(params[:post])
#post.upv, #post.downv, #post.points = 0, 0, 0
#post.user_id = params[:user_id]
#post.names = ""
if #post.save
redirect_to root_url, notice: "Post created."
else
render "new"
end
end
My form view is is just like any other form view.
instead of Post.new I typed User.new, SOLVED!!!

Resources