Rails best_in_place Unprocessable Entity - ruby-on-rails

I'm trying to edit the name field of a model with the best_in_place gem to edit items directly in line. When trying to do so I'm getting a an error 422 Unprocessable Entity.
I've done some research and found out in the response the controller is expecting not only the name attribute but also the group and some other attributes.
["Group must exist","Lumo can't be blank","Lumo is not a number"]
I have setup my controller in the correct way (I think).
materials_controller.rb
def update
#material = Material.find(params[:id])
if params[:commit] == "Save" then
success = #material.update(material_params)
params[:create_pure_composition] = false
else
#material = Material.new(material_params)
#material.user_id = current_user.id
success = #material.save
end
respond_to do |format|
if success
plot1 = prepare_e_plot(#material)
plot2 = prepare_st_plot(#material)
format.html { redirect_to #material }
format.json { render json: { plot1: plot1, plot2: plot2, status: 200 } }
else
format.html { render 'edit' }
format.json { respond_with_bip(#material) }
end
end
end
Is there a way with best_in_place to send these value's when updating the name attribute? I've tried with the params attribute from best_in_place.
<%= best_in_place #material, :name, as: :input, params: { group_id: #material.group_id } %>
This wasn't sending any extra params with the update.
Here's is what the Material model looks at.
material.rb
class Material < ActiveRecord::Base
belongs_to :user
belongs_to :group
validates :name, presence: true, uniqueness: {scope: :user_id}
validates :lumo, presence: true, numericality: true
end
Does anybody know why it's asking for other attributes and why best_in_place is not sending those along?

I figured out what the problem was and how to fix it. We use an option to Save or Save As when editing materials. In the controller we therefore check for the params[:commit].
By editing the url I was able to send in the params[:commit] with the update with best_in_place. Here is how the best_in_place code ended up like:
<%= best_in_place #material, :name, as: :input, url: material_path(#material, commit: "Save") %>

Related

How to fix Error ActiveRecord::RecordNotSaved when calling ActiveRecord#find_or_create_by

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.

Rails Simple Form - Add an error not related to attribute

I wanna do a validation that sums the values of nested fields so I make sure it's 100%. So, in my parent model, I would do the validation, and do a self.errors.add and add the error if the validation fails. The problem is, the errors.add as long as I know expects some attribute as argument, but it's not related to any attribute on my parent model, so I would like to display that message on the top of the form, for example. Any ideas of how I can do that? thank you!
UPDATE:
This is my parent model, where I wanna validate. The form has nested fields for :arrendamento_contrato_unidades.
class ArrendamentoContrato < ApplicationRecord
has_many :arrendamento_contrato_unidades, dependent: :destroy
validate :check_total_percentual_credito
def check_total_percentual_credito
if arrendamento_contrato_unidades.sum(&:percentual_credito).to_f != 100.0
self.errors.add :base, "Tem que ser 100%"
end
end
end
My create method, which it's the one I'm testing:
def create
#arrendamento_contrato = ArrendamentoContrato.new(arrendamento_contrato_params)
respond_to do |format|
if #arrendamento_contrato.save
format.html {
flash[:notice] = flash_notice
redirect_to action: "index"
}
format.json { render json: {status: 1, redirect: arrendamento_contratos_url} }
else
format.html { render :new }
format.json { render json: {status: 0, errors: #arrendamento_contrato.errors, status: :unprocessable_entity} }
end
end
end
--- Also, I debugged my object.errors.full_messages on the form, and the error is there. It's only not being displayed!
I guess that add errors to the base it's what I'm looking for. But now, it's not showing my message, but only that I have validation errors. My form code:
= simple_form_for(#arrendamento_contrato, validate: true, html: { id:"dropzoneForm", class: "dropzone dz-clickable"}) do |f|
= f.error_notification
.form-inputs
.row
.col-md-6
= f.input :numero
.col-md-6
= f.association :usina, input_html: {class: "chosen-select"}
.hr-line-dashed
.row
.col-md-6
= f.association :esco_contrato, input_html: {class: "chosen-select"}
.col-md-6
= f.association :om_contrato, input_html: {class: "chosen-select"}
.hr-line-dashed
.row
.col-md-4
= f.input :data_inicio, as: :string, input_html: {"data-mask" => "date"}
.col-md-4
= f.input :data_fim, as: :string, input_html: {"data-mask" => "date"}
.col-md-4
= f.input :valor_mensal, as: :string, input_html: {"data-mask" => "decimal"}
.hr-line-dashed
#arrendamento_contratos_unidades
- if !#arrendamento_contrato.arrendamento_contrato_unidades || #arrendamento_contrato.arrendamento_contrato_unidades.empty?
h3 = I18n.t('activerecord.models.unidade_consumidora.other')
i
'Aguardando ESCO...
- else
.row
.col-md-6
label class='control-label'
= I18n.t('activerecord.models.unidade_consumidora.other')
.col-md-6
label class='control-label'
= I18n.t('activerecord.attributes.arrendamento_contrato_unidade.percentual_credito')
.hr-line-dashed
.blockquote
= f.simple_fields_for :arrendamento_contrato_unidades do |f|
= render 'arrendamento_contrato_unidade_fields', f: f
.hr-line-dashed
i guess it should work for you
https://apidock.com/rails/ActiveRecord/Errors/add_to_base
just
errors.add_to_base("")
I think Jeff was on the right path, but I think the method you are supposed to use is model_instance.errors[:base].
I think you also might want to take into account the over all design of this feature (not that I have the full context of your app). if you have a validation of the parent model on its children model's it means that you will be saving erroneous children model to your data base only to then notify the user. Since it seems like this would be done with nested attribute you may want to consider doing this in the controller but there is an argument to be made about having too much logic in your controller.
Updating the answer for this common question and if anybody finds it useful. You can use Errors#add(:base, msg) instead
I.E.
def validate_method
errors.add(:base, 'Error message') if some_logic
...
end

Multiple Select Tag in Rails

Im trying to implement a multiple level drop down list in Rails
I have three Tables in my DB.
vehicle_make.rb
class VehicleMake < ActiveRecord::Base
validates_uniqueness_of :make
has_many :appointments
end
vehicle_model.rb
class VehicleModel < ActiveRecord::Base
validates_uniqueness_of :model
has_many :appointments
end
vehicle_make_model.rb
class VehicleMakeModel < ActiveRecord::Base
validates_uniqueness_of :vehicle_make_id, :scope => :vehicle_model_id
end
and im trying to implement a multiple dropdown list in appointments.html.rb
on selecting the vehicle model only corresponding make should load..
<%= f.select :vehicle_make_id, options_for_select(vehicle_make.map {|s| [s.make, s.id]}, appointment.vehicle_make_id), {}, {class: "form-control"} %>
and in my js i have..
$('#appointment_vehicle_make_id').on('change', function() {
var vehicle_make_id = this.value;
$.ajax({
url : '/appointments/update_models',
type : 'GET',
data : {
make_id : vehicle_make_id
},
success : function(response) {
console.log(response);
}
});
});
and this is my controller method.
def update_models
#vehicle_models = VehicleModel.all
#model_ids = []
#selected_vehicle_models = VehicleMakeModel.where(vehicle_make_id: params[:make_id]).order(:vehicle_model_id) unless params[:make_id].blank?
#selected_vehicle_models.each do |t|
#model_ids << t.vehicle_model_id
end
respond_to do |format|
format.html { render layout: false }
format.js
end
end
I have html page named update_models.html.erb associated to the above action.
<%= select_tag :vehicle_model_id, options_for_select(#model_ids.map {|s| [s.model, s.first.id]}, nil), {}, {class: "form-control"} %>
I get an error in terminal saying
ActionView::Template::Error (wrong number of arguments (4 for 1..3)):
1: <%= select_tag :vehicle_model_id, options_for_select(#model_ids.map {|s| [s.model, s.first.id]}, nil), {}, {class: "form-control"} %>
Im stuck here. I dont know how to proceed from here.. please help
In your controller action update_models, you are trying to render js, so it's trying to find template named as update_models.js.erb.
You can try replacing your respond_to block with:
respond_to do |format|
format.json { render :json => #model_ids }
end
Afterwards, you will need to parse this data in your ajax success callback

How to handle strong parameters as a array in Nested Form? Rails 4

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)

Rails unable to get before_destroy to call when deleting

I've been at this for a while and need a smarter person's opinion. I'm trying to check the passcode I have in my database matches the passcode the user enters in order to delete a record. I believe that I should be doing this in the model, and I am refactoring to use the before_destroy method. However I can't even get the before_destroy method to execute when I click on the delete button I made. The controller does execute the destroy method though.
View
<%= button_to('Destroy', #dish, method: "delete") %>
Model - the puts passcode_check? is never called from what I see
class Dish < ActiveRecord::Base
before_destroy :passcode_check?
validates :username, presence: true
validates :passcode, presence: true
validates :guests, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
private
def passcode_check?
puts "passcode_check?"
if #dish.passcode != params[:pass]
#dish.errors.add( :base, 'Unable to delete record; Reason: Passcodes did not match.')
return false
else
#dish.errors.add( :base, 'test.')
return false
end
end
end
Controller - this method is influx as I want to validate in the model
def destroy
if #dish.passcode == params[:pass]
#dish.destroy unless #dish.errors
respond_to do |format|
format.html { redirect_to dishes_url, notice: 'Record delete.' }
format.json { head :no_content }
end
else
respond_to do |format|
format.html { redirect_to #dish, action: 'show', notice: 'Unable to delete record; Reason: Passcodes did not match.' }
format.json { render json: #dish.errors, status: :unprocessable_entity }
end
end
end
In your controller you're using #dish.errors which will always return an ActiveModel::Errors object and therefore be truthy. So the unless #dish.errors statement modifier never lets #dish.destroy run and consequently neither will your callback. Change it to:
#dish.destroy if #dish.errors.empty?
And that should be it. Although it doesn't make much sense to check for errors yet since no validations have even run. Just call #dish.destroy and let your before_destroy callback halt the deletion by returning false and, conversely, let the deletion happen by returning true.

Resources