First or create method , creating duplicates - ruby-on-rails

Hi i'm trying to use the find or create method to update a skill if it already exists or create it if it doesn't. I can create a new skill fine but when i try update a skill that already exists it does update the skill but also creates a duplicate skill with the same data.
def create
#project = Project.find params[:project_id]
#skills_required = #project.skills_requireds.new(skills_required_params)
skills = SkillsRequired.where(skills_required_params.slice(:skill_id)).first_or_create
skills.update(skills_required_params)
respond_to do |format|
if #skills_required.save
format.html{ redirect_to #project, notice: "Skills required added for #{#project.projectName}" }
else
format.html{ redirect_to #project, notice: "Something went wrong, unable to update required skills " }
end
end
end
Form:
<div class="section">
<div class="top-border left"></div>
<div class="top-border right"></div>
<h3> Skill Required</h3>
<%= form_for([#project, SkillsRequired.new]) do |f| %>
<div class="field">
<%= f.label :skill_id %><br>
<%= f.collection_select :skill_id, Skill.all, :id, :skillType, :prompt => "Select Skill" %>
</div>
<div class="field">
<%= f.label :numWorkers %><br>
<%= f.number_field :numWorkers %>
</div>
<div class="field">
<%= f.label :skillLevel %><br>
<%= f.text_field :skillLevel %>
</div>
<%=f.submit "Add Skill" %>
<%end%>
</div>
I've tried adding a skills_required destroy to my controller but this doesn't allow me to add a new skill. Any help would be appreciated

You're approaching the problem wrong. You need a model level validation which enforces what skill requirements are valid:
# Your model names should be nouns - not adjectives
class SkillRequirement < ApplicationRecord
validates_uniqueness_of :skill_id, scope: :project_id'
end
If you want users to be able to specify the same skill but at different levels you can do it like so:
# Your model names should be nouns - not adjectives
class SkillRequirement < ApplicationRecord
validates_uniqueness_of :skill_id, scope: [:project_id, :level]
end
You should also combine this with a database index to avoid race conditions.
add_index(:skill_requirements, [:project_id, :skill_id], unique: true, name: 'by_skill_and_project')
Since updating existing skill requirements should be handled by a seperate update action you don't need all that bloat in your controller:
class SkillRequirementsController
before_action :set_project
def create
#skill_requirement = #project.skill_requirements.new(skill_requirement_params)
if #skill_requirement.save
redirect_to #project
else
render :new
end
end
def update
#skill_requirement = #project.skill_requirements.find(params[:id])
if #skill_requirement.update(skill_requirement_params)
redirect_to #project
else
render :edit
end
end
private
def set_project
#project = Project.find(params[:project_id])
end
def skill_requirement_params
params.require(:skill_requirement).permit(:)
end
end

Related

Rails - Does Build affect how many child records are created?

One Article has_many Images. When creating a new Article, Users can add 2 images max.
In my controller I run "build" for images only twice, but when I submit the form that has 3 image fields, it succeeds. Is there any need to run "build" at all? It seems pointless in this scenario, is there another way to better ensure only 2 images are accepted?
articles_controller.rb
def new
#article = Article.new
2.times { #article.images.build }
end
Note the "2.times" here.
def create
#article = Article.new(place_params)
#article.user = current_user
respond_to do |format|
if #review.save
params[:images][:image_file].each do |image_params|
#image = #article.images.create(image_file: image_params, user: current_user)
end
end
end
end
_form.html.erb
<%= form_with(model: article, url: create_article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images, #image do |image| %>
<div class="field">
<%= image.label :image_file_1, "Image 1" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_1 %>
</div>
<div class="field">
<%= image.label :image_file_2, "Image 2" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_2 %>
</div>
<div class="field">
<%= image.label :image_file_3, "Image 3" %>
<%= photo.file_field :image_file, name: "images[image_file][]", id: :article_images_image_file_3 %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
SUCCESS (But why?)
In short -- Your build statement is prepping the view to have 2 child objects. But you're manually creating them, so you're rendering the build statement as useless. You don't have to do it this way, you can declare nested attributes in the model, then whitelist in the controller, then auto-add them in the view. (see code example below)
Build itself does change how many objects are instantiated, but you're overriding that.
You are also manually saving the images, which you do not have to do. There's a bit of rails magic that saves all the children for you, if you've built them properly.
CodeView
1 The Model
app/models/article.rb
class Article < ApplicationRecord
has_many :images
validates :images, length: {maximum: 2}
accepts_nested_attributes_for :images
end
2 bits of note here. Firstly, in your validation, only allow 2 object, if you try to save a third, it will fail. Secondly, accepting the attribute in the model allows you to create safe params in the controller, thus alleviating your need to manually create. (unless of course, you really want to)
2 The View
<%= form_with(model: article, url: article_path(#article), local: true) do |form| %>
<div class="field">
<%= form.label :title %>
<%= form.text_area :title %>
</div>
<%= form.fields_for :images do |image_form| %>
<div class="field">
<%= image_form.label "image_file_#{image_form.index + 1}" %>
<%= image_form.file_field :image_file %>
<%= image_form.hidden_field :user_id, value: current_user.id %>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
The change here is a) I added the user directly to the form b.) because you are accepting attribute in the model and we'll whitelist the attribute in the controller, you don't need to pass an object to the field_for -- :images will do just fine. And because you will say to build it twice in your controller, you'll have 2 image objects in the form. Additionally, because you wanted a label of Image 1 and Image 2, with fields_for you automatically get access to the index of the object (just like you'd have with any array) by calling object.index.
3 The Controller - New Action
app/models/article.rb
Your action works perfectly well, keep it just as it is.
def new
#article = Article.new
2.times { #article.images.build }
end
4 The Controller - Strong Params
def article_params
params.require(:article).permit(:title, :body, images_attributes: [:id, :article_id,:user_id, :image_file])
end
Whitelisting your params altogether will save time and it's easier to read than permitting them in each controller, though you CAN do that if you need to, for instance if params are allowed in certain actions but not in others.
5 The Controller - Create Action
def create
#article = Article.new(article_params)
respond_to do |format|
if #article.save
format.html { redirect_to #article, notice: 'Article was successfully created.' }
format.json { render :show, status: :created, location: #article }
else
format.html { render :new }
format.json { render json: #article.errors, status: :unprocessable_entity }
end
end
end
This will probably look similar if not identical to the default create action in a scaffold, and this is all you need. The child image objects will not be created unless the parent can be created, so you don't have to worry about adding them in an if save.

Cannot create an new interface when it belongs to project

I try to create a new interface object. After clicking create button, it still remains new.html.erb, it should go to project_interfaces_path(main page). Also, the data has not saved yet.
I have tried many ways such as change URL, but it does not work and it reports NoMethodError in InterfacesController#create
undefined method `interfaces' for nil:NilClass
The interface/new.html.erb
<div class="card-body">
<%= form_for #interface, url:project_interfaces_path,method: :post do |f| %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_area :name,class: 'form-control'%>
</div>
<div class="form-group">
<%= f.label :desp %>
<%= f.text_field :desp,class:'form-control'%>
</div>
<div class="form-group">
<%= f.label :request_url %>
<%= f.text_field :request_url,class:'form-control'%>
</div>
<div class="form-group">
<%= f.label :request_eg %>
<%= f.text_field :request_eg,class:'form-control'%>
</div>
<div class="form-group">
<%= f.label :response_eg %>
<%= f.text_field :response_eg,class:'form-control'%>
</div>
<%=link_to project_interfaces_path do%>
<button type="button" class="btn btn-primary">返回列表</button>
<% end %>
<%=f.submit "创建",class: 'btn btn-primary' %>
<% end %>
The interface controller:
def new
#interface = Interface.new
end
def create
#interface = #project.interfaces.new(interface_params)
if #interface.save
redirect_to project_interfaces_path
else
render :new
end
end
private
def interface_params
params.require(:interface).permit(:id, :name, :desp,:request_url,:request_eg,:response_eg)
end
The interface belongs to project:
class Interface < ApplicationRecord
belongs_to :method_type
has_many :get_fields, dependent: :destroy
has_many :put_fields, dependent: :destroy
belongs_to :project
end
You're working with nested resources, it means you can't create an interface without project_id, since Interface belongs_to :project. How it should be:
def new
#project = Project.find(params[:project_id])
#interface = #project.interfaces.new
end
def create
#project = Project.find(params[:project_id])
#interface = #project.interfaces.build(interface_params)
if #interface.save
redirect_to project_interfaces_path(#project)
else
render :new
end
end
private
def interface_params
params.require(:interface).permit(:id, :name, :desp,:request_url,:request_eg,:response_eg)
end
And remove url and method options from form, it works automagically
<%= form_for #interface do |f| %>
Indeed, you are redirecting to new instead of project_interfaces_path:
def create
#interface = Interface.new(interface_params)
if #interface.save
#redirect_to new_project_interface_path(project) <- wrong path
redirect_to project_interfaces_path # Good path
else
render :new
end
end
Also, add a space between url: and project_interfaces_path in <%= form_for #interface, url:project_interfaces_path,method: :post do |f| %>.
UPDATE: It seems you are trying to save an Interface without associate a Project to it.
You need to retrieve a project and build the interface with it:
def new
project = Project.find(params[:id]) # Assuming you are sending it
#interface = project.interfaces.build
end
def create
project = Project.find(params[:id]) # Assuming you are sending it
#interface = project.interfaces.build(interface_params)
if #interface.save
redirect_to project_interfaces_path
else
render :new
end
end
Taking a look on your routes would help.

Creating new registration and new order at the same time

I'm trying to add an event registration to a current or new order. Question at the end of the post.
Event model: Contains the basic event information like title, date, description. This event model has many event options.
Event Option: Contains a description and a price. This event option has many registrations.
Registration: Allows the user to register and it takes the price from the event option price. This registration belong to an event option and to the order model.
Order: Calculates the total of the order based on the sum of all the registrations associated with it.
Creating a new registration:
In the event option show page, I have a form that creates a new registration using remote: true.
Here's the form:
<%= form_for(#registration, remote: true) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :lastname %><br>
<%= f.text_field :lastname %>
</div>
<div class="field">
<%= f.label :event_option_id %><br>
<%= f.text_field :event_option_id, value: #event_option.id %>
</div>
<div class="field">
<%= f.label :order_item_id %><br>
<%= f.text_field :order_item_id%>
</div>
<div class="field">
<%= f.label :price %><br>
<%= f.text_field :price, value: #event_option.price%>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When the form is submitted, it creates the event and also creates a new order if the order does not exist. To create the new order I have this helper method in the application controller:
def current_order
if !session[:order_id].nil?
Order.find(session[:order_id])
else
Order.new
end
end
Here's the create method in the registrations controller:
def create
#order = current_order
#registration = Registration.new(registration_params)
#order_id = current_order.id
respond_to do |format|
if #registration.save
format.html { redirect_to #registration, notice: 'Registration was successfully created.' }
format.json { render :show, status: :created, location: #registration }
format.js {}
#order.save
session[:order_id] = #order.id
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
end
The problem is that I'm not able add the registration to the order. I'm guessing that this is happening because the registration is created before the order. The last two line of the if #registration.save in the respond_to block are saving the order. How can I add the registration to the order? Can both, the new registration and new order be created at the same time?
A simple way to get around this is to assign the registration to the order before saving the order...
#order.registrations << #registration
#order.save
Alternatively you could create the association at the time you're creating the #registration record.
Instead of...
#registration = Registration.new(registration_params)
do....
#registration = #order.registrations.build(registration_params)

Rails 4, how to update a model field from a different controller?

I am trying to update an invoice fields, when checking out in the carts controller. These must be present when checking out, or it should fail. However, I can't get it to update, much less validate them.
Here is my code:
cart show view:
<div class = "row">
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Customer: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Seller: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Please Select'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<%= submit_tag 'Complete', class: 'btn btn-success' %>
<% end %>
</div>
</div>
carts controller:
class CartsController < ApplicationController
def show
#invoice = current_invoice
#invoice_products = current_invoice.invoice_products
#customers = Customer.all
#employees = Employee.all
end
def checkout
current_invoice.customer_id = params[:customer_id]
current_invoice.employee_id = params[:employee_id]
current_invoice.save
redirect_to current_invoice
end
end
current_invoice is the current session's invoice, related to the cart. It redirects correctly, but doesn't update.
in the invoices controller:
def invoice_params
params.require(:invoice).permit(:invoice_number, :customer_id, :invoice_date, :invoice_status_id, :employee_id, invoice_products_attributes: [:id, :invoice_id, :product_id, :price, :tax, :discount, :value])
end
Can anyone please help me in identifying where I am going wrong? Could it be my approach is not even valid?
Thanks in advance
The type of functionality you're after is considered "business logic" and should be implemented in the model and called from the controller.
You can define a method in a model:
class Invoice < ActiveRecord::Base
def update_invoice(cust_id, emp_id)
if self.update_attributes(:customer_id => cust_id], :employee_id = emp_id])
puts "Success!
else
puts "Failed to update record. Handle the error."
end
end
You can call my_method from carts_controller.rb like this:
def update
# all your regular update logic here
# replace the bit of code that saves the cart with something like this:
respond_to do |format|
if(current_invoice.update_invoice(params[:customer_id], params[:employee_id])
if(#cart.update(cart_params))
format.html { redirect_to #activity, notice: 'Activity was successfully updated.' }
format.json { render :show, status: :ok, location: #activity }
else
format.html { render :edit }
format.json { render json: #activity.errors, status: :unprocessable_entity }
end
end
end
Also, note the use of update_attributes rather than save. Bear in mind that update_attributes will return false if you run into any problems updating (e.g. one or more validations failed). Don't confuse update_attributes with the singular update_attribute which updates a single field and will not run validations.
Finally got it.
current_invoice.update_attributes(customer_id: params[:invoice][:customer_id], employee_id: params[:invoice][:employee_id])
Also in view, changed location of form_tag:
<div class = "row">
<%= form_tag carts_checkout_path, method: :post do |f| %>
<div class = "col-lg-3 col-lg-offset-6 text-left">
<strong>Cliente: </strong>
<%= collection_select(:invoice, :customer_id, #customers, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-3 ext-left">
<strong>Vendedor: </strong>
<%= collection_select(:invoice, :employee_id, #employees, :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
</div>
<div class = "col-lg-12 text-right">
<%= submit_tag 'Completar', class: 'btn btn-success' %>
</div>
<% end %>
</div>
Could it be my approach is not even valid
Your approach is definitely valid, it's great that you're using sessions in this way.
I'd do it slightly differently:
#config/routes.rb
resource :cart, except: [:edit, :new, :create], path_names: { update: "checkout" }
This will give you the following paths:
#url.com/cart -> carts#show (here you can invoke a cart if one doesn't exist)
#url.com/cart/checkout #-> POST to "update" method in carts controller
#url.com/cart/ (method: :delete) -> DELETE to "destroy" cart (refresh)
--
#app/controllers/carts_controller.rb
class CartsController < ApplicationController
before_action :setup_cart
def show
#cart = current_cart #-> products called from this. I don't know how you're linking them
#customers = Customer.all
#employees = Employee.all
end
def update
#invoice = Invoice.find_or_create_by(id: current_card.id)
#invoice.update update_params
redirect_to cart_path
end
def destroy
current_cart = nil
redirect_to carts_path, notice: "Cart Cleared"
end
private
def setup_cart
current_cart ||= sessions[:cart]
end
def update_params
params.require(:cart).permit(:customer_id, :employee_id)
end
end
Now, to update the cart, you'll want to take note from MarsAtomic's answer. However it must be noted that naked params are not available in the model.
If you use update_attributes, or just plain update, you'll need to do the following:
#app/models/cart.rb
class Invoice < ActiveRecord::Base
has_many :products
belongs_to :employee
belongs_to :customer
#validations here
#callbacks here (what MarsAtomic refers to as business logic)
before_save :do_something, only: :update
private
def do_something
#something here
#params appended to current instance of object
#eg self.customer_id
end
end
I'd also go more succinct in your view:
#app/views/carts/show.html.erb
<div class = "row">
<%= form_tag cart_checkout_path, method: :patch do |f| %>
<% options = [["cliente", "customer"], ["vendedor", "employee"]] %>
<% options.each do |name, type| %>
<%= content_tag :strong, "#{name.titleize}:" %>
<%= collection_select :cart, eval(":#{type}_id"), instance_variable_get("##{type.pluralize}"), :id, :full_name, {:prompt => 'Favor Seleccionar'}, class: 'form-control') %>
<% end %>
<% content_tag :div, class: "col-lg-12 text-right" do %>
<%= submit_tag 'Completar', class: 'btn btn-success' %>
<% end %>
<% end %>
</div>

Cannot enter simply form information into SQLite DB (Rails)

So, I'm running into a fairly simple problem, where I cannot enter some simple form values into my SQLite DB (Rails).
Interestingly, the code doesn't fail - I submit the form, and am redirected successfully to the correct URL - and a record IS inserted into the DB, but the columns "name, type and user_id" are not filled with anything. To clarify, the columns are blank, for that new record.
If I comment out the code in the "create" and simply spit out the params (render plain: params[:plan].inspect) I do see all the correct parameters filled out, so I have a feeling there must be something wrong in the line:
#plan = Plan.new(params[:plan])
I'm really stuck here, any guidance would be much appreciated!
The create form
<h1> Create a new plan </h1>
<%= form_for :plan, url: plans_path do |f| %>
<p>
<%= f.label :name %><br>
<%= f.text_field :name %>
</p>
<p>
<%= f.label :type %><br>
<%= f.text_field :type %>
</p>
<%= f.hidden_field :user_id, :value => current_user.id %>
<p>
<%= f.submit %>
</p>
<% end %>
plans_controller.rb
class PlansController < ApplicationController
def index
#plans = Plan.all
end
def show
#plan = Plan.find(params[:id])
end
def new
#plan = Plan.new
end
def create
#render plain: params[:plan].inspect
params.permit!
#plan = Plan.new(params[:plan])
if #plan.save
redirect_to #plan
else
redirect_to dashboard_path, :notice => "Plan NOT Created!"
end
end
end
The Model
class Plan < ActiveRecord::Base
end
Edit the plans_controller.rb:-
def create
#render plain: params[:plan].inspect
#plan = Plan.new(plan_params)
if #plan.save
redirect_to #plan
else
redirect_to dashboard_path, :notice => "Plan NOT Created!"
end
end
private
def plan_params
params.require(:plan).permit(:name,:type,:user_id)
end
Change the field name type as :-
rails g migration rename_fields_in_plans
class RenameFieldsInPlans < ActiveRecord::Migration
def change
rename_column :plans, :type, :plan_type
end
end
Then run the command:-
rake db:migrate

Resources