I have a simple app in which I want to allow the admin to create a new company. My create method in the controller is as follows:
def create
#company = Company.find_by({ name: company_create_params[:company_name] })
if #company.nil?
#company = Company.build_for(company_create_params)
else
return render :status => 200, :json => {
:error => true,
:reason => 'This company already exists in the database!'
}
end
if #company.save
return render :status => 200, :json => {
:success => true
}
else
return render :status => 500, :json => {
:error => true,
:reason => 'There was a problem adding the company'
}
end
end
private
def company_create_params
params.require(:company).permit( :company_name, :company_total_credits )
end
And my company model is:
class Company < ActiveRecord::Base
has_many :role
end
But every time I make an API post it gives me an error Undefined methodbuild_forfor class #<....>
Is it because of the has_many relationship? I don't want to add any value for the roles, rather I want them to be able to do it later on. Is there no way to fix this?
ActiveRecord doesn't provide a build_for method, hence the error.
You probably meant build, which is a method defined on collection associations. In this case, you probably want new or create since Company is a model, not an association.
Your whole action could be reduced significantly by following some conventions, by the way:
class Company < ActiveRecord::Base
has_many :roles
validates :company_name, uniqueness: true
end
# controller
def create
#company = Company.new(company_create_params)
if #company.save
render json: { success: true }
else
render status: 500, json: {
error: true,
reason: #company.errors.full_messages.to_sentence
}
end
end
Related
I am working on a website and whenever someone forgets a null field in the form I get an error saying
You cannot call create unless the parent is saved
This is the trace:
Application Trace
app/views/technicians/offer_comments/_offer_comment.html.slim:1:in `_app_views_technicians_offer_comments__offer_comment_html_slim__1148413763742950523_70319840794240'
app/views/offer_comments/index.html.slim:2:in `_app_views_offer_comments_index_html_slim___917297770217289302_70319839456700'
app/views/shared/offers/_comments.html.slim:8:in `_app_views_shared_offers__comments_html_slim__3779418887197040636_70319839163900'
app/views/technicians/auctions/show.html.slim:98:in `block in _app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/helpers/application_helper.rb:14:in `rescue in cache'
app/helpers/application_helper.rb:6:in `cache'
app/views/technicians/auctions/show.html.slim:1:in `_app_views_technicians_auctions_show_html_slim___1454306351028108654_70319829646100'
app/controllers/technicians/offers_controller.rb:54:in `update'
The error appears in the first line of this html.slim view:
- offer_comment.read_receipts.find_or_create_by user: current_user
.comment id="offer-#{offer_comment.offer_id}-comment-#{offer_comment.id}"
.by
- if offer_comment.user == current_user
= t ".you"
- else
= t ".not_you"
= " - "
= t '.date', date: time_ago_in_words(offer_comment.created_at)
.content
= raw markdown offer_comment.content
The interesting part is that this error only occurs when I call another object, offers, in the main view in which the previous code is rendered: show.html.slim (last line)
ul#customer-auction-tabs.tabs.clean.collapse(data-tabs)
a#auctions-tabs-chevron href="#"
i#auctions-tabs-chevron-icon.fas.fa-chevron-up
li.tabs-title class=chat_active_class
a#chat-tab href="#chat" aria-selected="true"= t '.tabs.chat'
li.tabs-title class=offer_active_class
a#offers-tab href="#offers"= t '.tabs.offer'
- if comments_count > 0
li.tabs-title class=comments_active_class
a#comments-tab href="#comments"= t '.tabs.comments'
li.tabs-title class=other_active_class
a#other-tab href="#other"= t '.tabs.other'
.auctions.tabs-content data-tabs-content="customer-auction-tabs"
#chat.tabs-panel class=chat_active_class
= render partial: "shared/auctions/chat", locals: { auction: auction }
#offers.tabs-panel class=offer_active_class
= render partial: "shared/offers/new", locals: { offer: offer }
#comments.tabs-panel class=comments_active_class
= render partial: 'shared/offers/comments', locals: { offer: offer }
#other.tabs-panel class=other_active_class
- if auction.offers.count.zero?
= t "ingen andre bud endnu"
= render "shared/offers/other"
.offers= render offers
I don't understand how this works because find_or_create_by is apparently supposed to work even if the object hasn't been saved.
Can someone help me solve this issue, and preferably avoid using logic like find_or_create_by in the view at all?
Here is part of the Offer model:
class Offer < ApplicationRecord
has_paper_trail
belongs_to :auction, -> { with_deleted }, counter_cache: true, touch: true
belongs_to :technician, counter_cache: true, foreign_key: :technician_id
has_one :settings, through: :technician
has_many :comments, -> { order(created_at: :asc) }, class_name: "OfferComment"
has_one :review, as: :target
delegate :rating, to: :review, allow_nil: true
delegate :rating_date, to: :review, allow_nil: true
delegate :rating_comment, to: :review, allow_nil: true
validates :description, presence: true
validates :cents, presence: true, numericality: { only_integer: true, greater_than: 0 }
validate :amount_validity
scope :not_by, ->(technician) { where.not(technician: technician) }
Here is also the controller update action that gets called when updating the form with a null field:
class Technicians::OffersController < Technicians::ApplicationController
rescue_from ActiveRecord::RecordNotFound do
render "technicians/auctions/lost", status: 404
end
def update
offer.attributes = offer_params
changed = offer.changed?
if offer.save
OfferUpdatedWorker.perform_async offer.id if changed
flash[:info] = t(".success")
redirect_to [:technicians, auction]
else
flash.now[:error] = t(".failure")
render "technicians/auctions/show",
locals: { auction: auction, offer: offer },
status: 400
end
end
Another important file to note is the auction controller that originally calls "technicians/auctions/show"
def show
render(:lost) && return if lost?
render :show, locals: {
offers: sorted_o,
auction: auction,
#other: other,
offer: offer,
} if stale? **cache_options(auction.id, auction.updated_at)
end
private
#=begin
def sorted_o
#sorted_o ||= begin
field = (%w[cheapest closest guarantee] & [params[:sort]])[0].presence || "cheapest"
case field
when "closest"
auction
.offers
.includes(:auction, :technician, :review)
.sort_by { |o| distance(o.technician, auction) }
when "guarantee"
auction
.offers
.includes(:auction, :technician, :review)
.joins(:settings)
.order("technician_settings.guarantee desc")
else
auction
.offers
.includes(:auction, :technician, :review)
.order(cents: :asc)
end
end
end
#=end
def offer
#offer ||= auction.offers.by(current_user) ||
auction.offers.new(technician: current_user)
end
It looks like you need offer_comment to be saved before a read_receipt that belongs to it can be created, which makes sense - the offer_comment doesn't have an id until it has been saved.
This might get you past the problem.
offer_comment.tap(&:save).read_receipts.find_or_create_by user: current_user
I fixed it. The cause of the problem was that when the update action fails in offers_controller.rb it calls the show view without the offers variable, this variable is somehow related to the offer_comments, but I'm not sure how/why because offer_comments is supposed to be only related to the variable offer and not offers.
However, when I checked the console there were null elements in the offer_comments so I just went on and changed the view to only show non null elements, the error stack then pointed to offers not being defined in the show view.
In my Case model (case.rb) I have a few after_create callbacks. One of which is not functioning as expected.
By that I mean when I run it in the console, it works, but when creating it in the controller via an HTTP request, it does not.
Model code (the problem is with :create_case_users not working):
after_create -> { redis_check(true) },
:create_case_users,
:create_manager,
:pusher_trigger
And the create_case_users method:
def create_case_users
cus = CaseUser.where(:case_id => self.id, :is_age => 1)
cus.each do |cu|
connections = Connection.where({ :cases => 1, :con_id => cu.user_id })
connections.each do |connection|
if connection.user_id && connection.connection_id
case_user = CaseUser.new
case_user.case_id = self.id
case_user.user_id = connection.user_id
case_user.email = connection.cases_emails
case_user.is_age = 0
case_user.cm = 0
case_user.save!
end
end
end
end
When running the above snippet in the console of my app, specifying the Case to run it for, it works & creates the records I expect.
When I POST to /api/v1/cases, it hits this controller action:
#case = Case.new(case_params)
#case.current_user(#current_user)
if #case.save
render :json => #case,
:root => "case",
:status => :created,
:location => #case,
:serializer => CaseShowSerializer,
:current => #current_user
else
render :json => #case.errors,
:status => :unprocessable_entity
end
What could be the problem? The after create callback after create_case_users runs and functions as expected, meaning create_case_users isn't returning false.
Edit:
When running Case.find(500).create_case_users in the console it works.
The context is as follows, I have entities that can have multiple roles. These roles are manageable by the user.
For example, Entity named "Lipsum" may be "Cashier and Salesperson". So, this is a relation many_to_many.
So I have my 3 models: Entity, type_entity and entity_by_type
class Entity < ActiveRecord::Base
has_many :entity_by_types
has_many :type_entities, :through => :entity_by_types
accepts_nested_attributes_for :entity_by_types
end
class EntityByType < ActiveRecord::Base
belongs_to :entity
belongs_to :type_entity
end
class TypeEntity < ActiveRecord::Base
has_many :entity_by_types
has_many :entities, :through => :entity_by_types
end
I have an ordinary CRUD for entity types.
Now, in the CRUD of entities, I have a field Select-Option Multiple. In which the user chooses has 1 or more types, the entity that is creating.
Then my Controller Entity is as follows:
class Logistics::EntitiesController < ApplicationController
def index
#type_entities = TypeEntity.all
render layout: false
# I use this for show All entities by TypeEntity in my view index
end
def show
end
def new
#type_entities = TypeEntity.all
#entity = Entity.new
render layout: false
end
def create
entity = Entity.new(entity_parameters)
if entity.save
flash[:notice] = "Succesfull!."
redirect_to :action => :index
else
flash[:error] = "Error."
redirect_to :action => :index
end
end
def edit
#entity = Entity.find(params[:id])
#type_entities = TypeEntity.all
#action = 'edit'
render layout: false
end
def update
entity = Entity.find(params[:id])
entity.update_attributes(entity_parameters)
flash[:notice] = "Succesfull."
redirect_to :action => :index
end
def destroy
#entity = Entity.destroy(params[:id])
render :json => #entity
end
private
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, :type_entity_id])
end
end
And my partial form (for method create and Update) is:
= simple_form_for([:namespace, #entity], html: {class: 'form-horizontal' }) do |f|
= f.input :name, placeholder: "Nombre", input_html: { class: 'form-control' }, label: false
= f.input :surname, placeholder: "Apellidos", input_html: { class: 'form-control' }, label: false
%select.select2#type-entity-select{:name => "entity[entity_by_types_attributes][type_entity_id][]", :style => "width:100%;padding: 0;border: none;", :multiple => true}
- #type_entities.each do |tent|
%option{value: "#{tent.id}"}
= tent.name
But, when I click in button submit, and "type_entity_id" have 1 or more values; in my database only display a 1 record where, entity_id is OK, however type_entity_id is NULL.
Moreover only view a 1 record, when should see 1 or more records, depending on the number of types of choice in the form.
The problem here is the way of pass type_entity_id in form of array. So, How I can do that?
P.D
The following is how the params go to my controller:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ASD"1231+Dssr6mRJcXKh9xHDvuVDmVl4jnwIilRBsuE=", "entity"=>{"name"=>"Lorem", "surname"=>"Ipsum", "entity_by_types_attributes"=>{"type_entity_id"=>["1", "4"]}}}
Try this:
def entity_parameters
params.require(:entity).permit(:name, :surname, entity_by_types_attributes: [:id, :entity_id, {:type_entity_id => []}])
end
Edit:
In your form and in def entity_parameters replace type_entity_id with type_entity_ids
Thus, the parameter will refer to a set (array) not to a single object. These are the generic method syntaxes:
Model.associate_id = some integer
Model.associate_ids = an array (for a has_many relation)
I am refactoring a project, and I remembered that I had some troubles in realizing how to put a nested object, but I found this question useful.
So, as I understand it, you needed to pass as a parameter your associated model name in plural and add a '_attributes' to it. It worked great in Rails 3.2.13.
Now, here is what I have in Rails 4:
class TripsController < Api::V1::ApiController
def create
begin
#user = User.find(params[:user_id])
begin
#campaign = #user.campaigns.find(params[:campaign_id])
if #trip = #campaign.trips.create(trip_params)
render json: #trip, :include => :events, :status => :ok
else
render json: { :errors => #trip.errors }, :status => :unprocessable_entity
end
rescue ActiveRecord::RecordNotFound
render json: '', :status => :not_found
end
rescue ActiveRecord::RecordNotFound
render json: '', :status => :not_found
end
end
private
def trip_params
params.require(:trip).permit(:evnt_acc_red, :distance, events_attributes: [:event_type_id, :event_level_id, :start_at, :distance])
end
end
And the Trip model looks like this:
class Trip < ActiveRecord::Base
has_many :events
belongs_to :campaign
accepts_nested_attributes_for :events
end
So, I am doing a POST call with the following JSON:
{"trip":{"evnt_acc_red":3, "distance":400}, "events_attributes":[{"distance":300}, {"distance":400}]}
And, even though I don't get any kind of error, no event is being created. The trip is being created correctly, but not the nested object.
Any thoughts on what should I do to make this work on Rails 4?
Alright, so... I was sending the JSON wrongly:
Instead of:
{
"trip": {
"evnt_acc_red": 3,
"distance": 400
},
"events_attributes": [
{
"distance": 300
},
{
"distance": 400
}
]
}
I should have been sending:
{
"trip": {
"evnt_acc_red": 3,
"distance": 400,
"events_attributes": [
{
"distance": 300
},
{
"distance": 400
}
]
}
}
I have a one to many relationship between EnquiryForm and UniversityFeeInstallment EnquiryForm has_many UniversityFeeInstallment.
Following is the params I receive from contoller
{
"utf8"=>"✓",
"authenticity_token"=>"jqgiRlk606pDzMEAtS/mGoWz8T61PgyCkKdMzSHEiQA=",
"enquiry"=>{
"university_fee_installments_attributes"=>{
"1338318502006"=>{
"due_date"=>"2012-05-28",
"amount"=>"1200"
}
}
},
"commit"=>"Update Enquiry",
"id"=>"4fc3db492d6d130238000028"
}
I am using Ryan Bates classic nested form technique.
Also model code is :
has_many :development_fee_installments, :autosave => true
has_many :university_fee_installments, :autosave => true
accepts_nested_attributes_for :development_fee_installments
accepts_nested_attributes_for :university_fee_installments
Controller:
def update
#enquiry = Enauiry.find(params[:id])
if #enquiry.save
redirect_to enquiry_payments_path(#enquiry, :notice => "Installment details updated")
else
render 'edit_installments'
end
end
I am not able to save university_fee_installments.
Change your controller code to this
def update
#enquiry = Enquiry.find(params[:id])
if #enquiry.update_attributes(params[:enquiry])
redirect_to enquiry_payments_path(#enquiry, :notice => "Installment details updated")
else
render 'edit_installments'
end
end
update_attributes will do the trick as we are passing the params we received from view in that.