Error: has-many relationship in form, duplicate data when update - ruby-on-rails

I am doing an e-commerce.
I have products which have many options of products and at the same time they only have one variant.
I try to make the view to create the product have the option of add a block where appears the fields of the model and the changes of the variant which is associated to it. The problem is, for example, when i create a product with 5 options, when i update it increases to 10, and if i update it again, there will be 20. I can't find the problem why they are duplicating. I leave the codes below.
Schema of Product, option-product and variant
create_table "options_products", force: :cascade do |t|
t.integer "product_id"
t.float "price"
t.integer "stock"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "products", force: :cascade do |t|
t.string "name", default: "", null: false
t.text "description"
t.integer "category_id"
t.integer "vendor_id"
t.string "state", null: false
t.boolean "shippingInside", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "priceComparison"
t.string "image1_file_name"
t.string "image1_content_type"
t.integer "image1_file_size"
t.datetime "image1_updated_at"
t.float "price"
end
create_table "variants", force: :cascade do |t|
t.string "tipoVariant"
t.integer "options_product_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "controlStock"
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
end
Script for add and remove fields
$(document).on 'ready page:load', ->
$('form').on 'click', '.remove_field', (event) ->
$(this).prev('input[type=hidden]').val('1')
$(this).closest('fieldset').hide()
event.preventDefault()
$('form').on 'click', '.add_field', (event) ->
time = new Date().getTime()
regular_expression = new RegExp($(this).data('id'), 'g')
$(this).before($(this).data('fields').replace(regular_expression,time))
event.preventDefault()
Product create and update
def create
#product = Product.new(product_params)
respond_to do |format|
if #product.save
format.html { redirect_to #product}
format.json { render :show, status: :created, location: #product }
else
format.html { render :new }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
def update
respond_to do |format|
if #product.update(product_params)
format.html { redirect_to #product}
format.json { render :show, status: :ok, location: #product }
else
format.html { render :edit }
format.json { render json: #product.errors, status: :unprocessable_entity }
end
end
end
Product Params
def product_params
params.require(:product).permit(:name, :description, :state, :shippingInside, :vendor_id, :category_id, :priceComparison, :image1, :price, offer_attributes: [:status], options_products_attributes: [:price, :stock, variant_attributes: [:tipoVariant, :controlStock, :image]])
function in application helper to add association
def link_to_add_association(name, field, association)
new_object = field.object.send(association).klass.new
new_object_id = new_object.object_id
fields = field.fields_for(association, new_object, child_index: new_object_id) do |builder|
new_object.build_variant
render(association.to_s.singularize + '_field', f: builder)
end
link_to(name, '#', class: 'add_field', data: { id: new_object_id, fields: fields.gsub("\n", "") })
end
Product model
class Product < ActiveRecord::Base
#relations
belongs_to :category
belongs_to :vendor
has_one :offer, :dependent => :destroy
has_many :options_products, :dependent => :destroy
#accepts
accepts_nested_attributes_for :offer, allow_destroy: true
accepts_nested_attributes_for :options_products, allow_destroy: true
#validations
validates :name, presence:true
validates :name, uniqueness:true
validates :state, presence:true
validates :category_id, presence:true
validates :vendor_id, presence:true
has_attached_file :image1, styles: {medium: "300x300>", thumb: "150x150#" }
validates_attachment_content_type :image1, content_type: /\Aimage\/.*\z/
end
Option Product Model
class OptionsProduct < ActiveRecord::Base
belongs_to :product
has_one :variant, :dependent => :destroy
accepts_nested_attributes_for :variant, allow_destroy: true
end
Variant model
class Variant < ActiveRecord::Base
belongs_to :options_product
has_attached_file :image,
styles: {medium: "300x300>", thumb: "150x150#" }
validates_attachment_content_type :image,
content_type: /\Aimage\/.*\z/
end
_form of Product
= form_for #product, html: { multipart: true } do |f|
.row
.form-group.col-lg-6
.field
= f.file_field :image1
.row
.form-group.col-lg-6
.field
= f.text_field :name, :placeholder => 'Nombre', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= f.text_area :description, :placeholder => 'Descripcion', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= f.number_field :price, :placeholder => 'Precio a mostrar', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= f.label :Estado
%br/
= f.select :state, options_for_select(['Disponible', 'No disponible'])
.row
.form-group.col-lg-6
.field
= f.label :Envio
%br/
= f.check_box :shippingInside
.row
.form-group.col-lg-6
.field
= f.text_field :priceComparison, :placeholder => 'Precio anterior', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= f.label :vendor_id
%br/
= f.select :vendor_id, Vendor.all.collect { |vendor| [vendor.name, vendor.id] }
.row
.form-group.col-lg-6
.field
= f.label :category_id
%br/
= f.select :category_id, Category.all.collect { |category| [category.name, category.id] }
= f.fields_for :offer, #product.build_offer do |o|
= o.label :Oferta
%br/
= o.check_box :status
%br/
.row
= f.fields_for :options_products do |op|
= render 'options_product_field', f: op
= link_to_add_association 'Agregar variante', f, :options_products
%br/
.actions
= f.submit "Enviar", :class => 'button btn btn-primary bold'
options_product_field file
%fieldset
.row
.form-group.col-lg-6
.field
= f.text_field :price, :placeholder => 'Precio', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= f.text_field :stock, :placeholder => 'Stock', :class => 'form-control input-border-left'
= f.fields_for :variant do |v|
.row
.form-group.col-lg-6
.field
= v.text_field :tipoVariant, :placeholder => 'Tipo de variante', :class => 'form-control input-border-left'
.row
.form-group.col-lg-6
.field
= v.label :ControlarStock
%br/
= v.check_box :controlStock
.row
.form-group.col-lg-6
.field
= v.label :ImagenDeVariante
%br/
= v.file_field :image
= f.hidden_field :_destroy
= link_to 'Remover variante', '#', class: 'remove_field'

In product_params, you should specify id of options_products_attributes. Without id, attributes will be newly added to product model.
So, try
... options_products_attributes: [ :id, price, :stock, variant_attributes: [ :id, :tipoVariant, ...

Related

UsersController Method Error 'title'. No Title in view?

Reposting due to formatting errors in previous post.
I am getting the below error, I've added code below for the whole MVC. The strange thing here is the controller wants a 'title' method to be included but I don't have a title requirement anywhere else, could this be referring to something else. While this isn't working the def update method won't patch so I can't save any user updates or new data to the system (it's a HRIS system btw).
So the error is;
NoMethodError in UsersController#update
undefined method `title' for #<User:0x007ff3dd9f8a70>
Extracted source (around line #56):
54
55
56
57
58
59
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Information Updated"
redirect_to #user
#handle successful update
Here is my Schema;
create_table "users", force: :cascade do |t|
t.string "name"
t.string "employee"
t.string "email"
t.string "password"
t.date "startdate"
t.date "probationcomp"
t.date "annualreview"
t.text "address"
t.string "phonenumber"
t.string "nextofkin"
t.string "kinnumber"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "password_digest"
t.boolean "admin", default: false
t.string "leavenum"
t.string "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.datetime "avatar_updated_at"
t.boolean "hstrain"
t.boolean "induction"
t.boolean "lhtrain"
t.boolean "vodatotal"
t.boolean "commtrainops"
t.boolean "commtrainsell"
t.boolean "hosttrainops"
t.boolean "hosttrainsell"
t.boolean "cpstrainops"
t.boolean "cpstops"
t.boolean "cpstsell"
t.string "leaverem"
t.text "specialism"
t.string "shelf"
t.string "doc_file_name"
t.string "doc_content_type"
t.integer "doc_file_size"
t.datetime "doc_updated_at"
end
The view;
<h1>Change your profile settings</h1>
<div class="row">
<%= form_for #user do |f| %>
<%= render 'shared/error_messages' %>
<!-- left side -->
<div class="col-md-6">
<%= f.label :password %>
<%= f.password_field :password, class: 'form-control' %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
<%= f.label :address, "Home Address" %>
<%= f.text_area :address, :cols => "1", :rows => "5", class: "form-control" %>
</div>
<!-- right side -->
<div class="col-md-6">
<%= f.label :phonenumber, "Contact Number" %>
<%= f.text_field :phonenumber, class: "form-control" %>
<%= f.label :nextofkin, "Emergency Contact Person" %>
<%= f.text_field :nextofkin, class: "form-control" %>
<%= f.label :kinnumber, "Emergency Contact Phone Number" %>
<%= f.text_field :kinnumber, class: "form-control" %>
<div class="gravatar_edit">
<%= gravatar_for #user %>
<a href="https://gravatar.com/emails" target=_blank>Change Profile Pic</a>
</div><br><p><br>
<%= f.submit "Save Changes", class: 'btn btn-warning' %>
<% end %>
</div>
</div>
The controller (this is where I think the error is, though update function looks fine, I haven't posted the whole controller, but can do if it helps)
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
flash[:success] = "Information Updated"
redirect_to #user
#handle successful update
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:name, :employee, :email, :password, :startdate, :probationcomp,
:password_confirmation, :annualreview, :address, :phonenumber, :nextofkin, :kinnumber,
:created_at, :updated_at, :password_digest, :admin, :leavenum, :avatar_file_name, :avatar_content_type, :avatar_file_size,
:avatar_updated_at, :hstrain, :induction, :lhtrain, :vodatotal, :commtrainops, :commtrainsell, :hosttrainops,
:hosttrainsell, :cpstrainops, :cpstops, :cpstsell, :leaverem, :specialism, :shelf, :doc_file_name, :doc_content_type,
:doc_file_size, :doc_updated_at)
end
Finally the model;
class User < ApplicationRecord
attr_accessor :remember_token
before_save { email.downcase! }
validates :name, presence: true, length: { maximum: 50 }
VALID_EMAIL_REGEX = /\A[\w+\-.]+#[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i
validates :email, presence: true,
format: { with: VALID_EMAIL_REGEX },
uniqueness: { case_sensitive: false }
has_secure_password
validates :password, presence: true, length: { minimum: 7 }, allow_nil: true
#set up "uploaded_file" field as attached_file (using Paperclip)
searchable do
text :name, :id, :phonenumber
end
def User.digest(string)
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST :
BCrypt::Engine.cost
BCrypt::password.create(string, cost: cost)
end
def User.new_token
SecureRandom.urlsafe_base64
end
def remember
self.remember_token = User.new_token
update_attribute(:remember_digest, User.digest(remember_token))
end
def authenticated?
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
end
def forget
update_attribute(:remember_token, nil)
end
end
I'm sure someone has had this issue before, just can't see the wood for the trees. test script comes back with same error.

Nested forms with join table attributes

I have a new Match form:
EDIT:
= form_for(#match) do |f|
= f.label :match_date
= f.date_select :match_date, :order => [:day, :month, :year]
= f.label :player_ids, 'Select players'
= f.collection_select :player_ids, #players, :id, :lastname, {}, { multiple: true }
= f.fields_for :match_edits do |ff|
= ff.label :result
= ff.number_field :result, in: 0..10
%div
= f.button :submit
When I choose two players I want set for each one match result like this:
id: 1, match_id: 1, player_id: 1, result: 4
id: 2, match_id: 1, player_id: 2, result: 10
I'm new in rails and I don't know how to fix that
MatchController
class MatchesController < ApplicationController
respond_to :html
def index
#matches = Match.all
end
def show
#match = Match.find(params[:id])
#results = #match.match_edits
end
def new
#match = Match.new
#players = Player.all
2.times {#match.match_edits.build}
end
def create
#match = Match.new(match_params)
respond_to do |format|
if #match.save
format.html { redirect_to #match, notice: 'Match was successfully created.' }
format.json { render :show, status: :created, location: #match }
else
format.html { render :new }
format.json { render json: #match.errors, status: :unprocessable_entity }
end
end
end
private
def match_params
params[:match].permit :match_date, player_ids: [], :match_edits_attributes => [:id, :result]
end
end
MatchEdit model
class MatchEdit < ActiveRecord::Base
belongs_to :match
belongs_to :player
end
Match model
class Match < ActiveRecord::Base
has_many :match_edits
has_many :players, through: :match_edits
accepts_nested_attributes_for :match_edits, allow_destroy: true, reject_if: proc { |attrs| attrs['result'].blank? }
end
Schema.rb
ActiveRecord::Schema.define(version: 20150629144534) do
create_table "match_edits", force: :cascade do |t|
t.integer "match_id"
t.integer "player_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "result"
end
add_index "match_edits", ["match_id"], name: "index_match_edits_on_match_id"
add_index "match_edits", ["player_id"], name: "index_match_edits_on_player_id"
create_table "matches", force: :cascade do |t|
t.date "match_date"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "players", force: :cascade do |t|
t.string "firstname"
t.string "lastname"
t.string "picture"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
You need to update your form to remove redundant code i.e.:
= ff.number_field :result, in: 0..10
Your form will look like:
= form_for(#match) do |f|
= f.label :match_date
= f.date_select :match_date, :order => [:day, :month, :year]
= f.label :player_ids, 'Select players'
= f.collection_select :player_ids, #players, :id, :lastname, {}, { multiple: true }
= f.fields_for :match_edits do |ff|
= ff.label :result
= ff.number_field :result, in: 0..10
%div
= f.button :submit
Your controller's new method is responsible to to provide multiple fields for result:
class MatchsController << ApplicationContoller
...
def new
...
2.times { #match.match_edits.build }
...
end
...
end
Your model should allow to accept nested attributes as following:
class Match
...
has_many :match_edits
accepts_nested_attributes_for :match_edits, allow_destroy: true,
reject_if: proc { |attrs| attrs['result'].blank? }
...
end
class MatchEdit
...
belongs_to :match
...
end
I found solution. Form should look like this:
= form_for(#match) do |f|
= f.label :match_date
= f.date_select :match_date, :order => [:day, :month, :year]
= f.fields_for :match_edits do |ff|
= ff.label :player_id, 'Select player'
= ff.collection_select :player_id, #players, :id, :lastname, {}, { multiple: false }
= ff.label :result
= ff.number_field :result, in: 0..10
%div
= f.button :submit
and in matches_controller:
def match_params
params[:match].permit :match_date, :match_edits_attributes => [:id, :result, :player_id]
end

Carrierwave not saving the file on disk

everyone. Please, help me with carrierwave and Rails 4.
Model:
class MediaItem < ActiveRecord::Base
mount_uploader :media_item, MediaItemUploader
end
Schema:
create_table "media_items", force: true do |t|
t.string "file"
t.text "description", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Uploader:
class MediaItemUploader < CarrierWave::Uploader::Base
def store_dir
"public/"
end
Controller:
class MediaItemsController < ApplicationController
def create
#media_item = MediaItem.new(media_item_params)
respond_to do |format|
if #media_item.save
format.html { redirect_to #media_item, notice: 'Media item was successfully created.' }
else
format.html { render :new }
end
end
end
Form:
= form_for #media_item, :html => {:multipart => true} do |f|
.field
= f.label :description
= f.text_field :description
.field
= f.label :file
= f.file_field :file
.actions
= f.submit 'Save'
The problem is that uploaded file does not appear in specified dir and "media_item.media_item_url" return empty string in a view. However, it save the recore into db:
select file, description from media_items;
#<ActionDispatch::Http::UploadedFile:0xba672374>|wetw
My mistake.
Instead
mount_uploader :media_item, MediaItemUploader
it must be
mount_uploader :file, MediaItemUploader
as "file" is a field name in db, not "media_item"

IF statements in HAML form not working as expected

I have an "Accept/Reject" workflow for tasks (created with nested forms via Cocoon). Basically,
if 1) the current user is the assignee (current_user.email == :assignee), 2) the current user is not the creator (current_user.email != :creator), and 3) the task has not been accepted (true) or rejected (false) i.e. nil (:accepted != nil),
then 1) show the creator (:creator) and 2) the enabled field to accept or reject (f.select :accepted)
When I am using these if statements, the fields (creator & accept/reject) are showing regardless of whether the condition is true or false. In addition, I am not allowed to use current_user.email in my if statement even though I can print it just fine.
Here's my code:
_task_fields.html.haml (views > projects)
.nested-fields
.field
= f.label :description
= f.text_field :description
.field
= f.label :done
= f.check_box :done
- if (:accepted != nil) && (:creator != nil)
.field
= f.label :creator
= f.text_field :creator, :disabled => true
.field
= f.label :accepted, "Accept or Reject"
= f.select :accepted, options_for_select([nil,['Accept',true],['Reject',false]])
.field
= current_user.email
= f.label :priority
- if :priority == nil
= f.select :priority, options_for_select(["None","Low","High"],"None")
- else
= f.select :priority, options_for_select(["None","Low","High"],:priority)
.field
= f.label :assignee
= f.select :assignee, User.pluck(:email), :prompt => "Select One"
= link_to_remove_association "remove task", f, :class => "btn btn-default btn-sm"
_form.html.haml (views > projects)
= form_for #project do |f|
.jumbotron
%h3 Project Settings
.field
= f.label :title
= f.text_field :title
#category
.form_row
= f.label :category
- if #project.category == "process"
= f.radio_button :category, "process", :checked => 'checked'
= "process"
= f.radio_button :category, "checklist", :checked => #project.category == "checklist"
= "checklist"
- else #project.category == "checklist"
= f.radio_button :category, "process", :checked => #project.category == "process"
= "process"
= f.radio_button :category, "checklist", :checked => 'checked'
= "checklist"
.field
= f.label :description
= f.text_field :description
- if #project.category == "process"
%h3 Steps
#steps
= f.fields_for :steps do |step|
%ul.list-group
= render 'step_fields', :f => step
.links
= link_to_add_association 'add step', f, :steps
- if #project.category == "checklist"
#tasks
= f.fields_for :tasks do |task|
.jumbotron
= render 'task_fields', :f => task
.links
.jumbotron
= link_to_add_association 'add task', f, :tasks, :partial => 'projects/new_task_fields', :class => "btn btn-default btn-sm"
= f.submit
projects_controller.rb (just the relevant parts)
def update
respond_to do |format|
if #project.update(project_params)
format.html { redirect_to #project, notice: 'Project was successfully updated.' }
format.json { render :show, status: :ok, location: #project }
format.js
else
format.html { render :edit }
format.json { render json: #project.errors, status: :unprocessable_entity }
format.js
end
end
end
def project_params
params.require(:project).permit(:title, :category, :description, tasks_attributes: [:id, :description, :done, :priority, :assignee, :creator, :created_at, :accepted, :completed_at, :_destroy])
end
schema.rb (just the relevant parts)
create_table "tasks", force: true do |t|
t.string "description"
t.boolean "done"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
t.datetime "due"
t.boolean "started"
t.boolean "active"
t.string "repeat"
t.integer "time_limit"
t.string "priority"
t.string "assignee"
t.string "creator"
t.datetime "started_at"
t.datetime "completed_at"
t.datetime "paused_at"
t.datetime "resumed_at"
t.integer "time_spent"
t.boolean "accepted"
end
add_index "tasks", ["project_id"], name: "index_tasks_on_project_id"
I can add more files if necessary but I doubt my controller/model seems to be the issue. Thanks and appreciate any input.
- if (:accepted != nil) && (:creator != nil)
The above statement will always be true, because you are checking if a symbol is not equal to nil which is always true
You should check something like
- if (#project.accepted != nil && #project.creator != nil)

When updating a _form partial that has an image, but not the image, I get an error

I am using Carrierwave & Fog to push images str8 to S3.
The creation action works just fine. The issue is whenever I go to update the record, say change the name attribute, and I don't do anything to the image fields, I get an error that looks like this:
Started PUT "/vendors/7" for 67.230.41.62 at 2012-12-09 07:00:51 +0000
2012-12-09T07:00:51+00:00 app[web.1]: app/controllers/vendors_controller.rb:65:in `update'
2012-12-09T07:00:51+00:00 app[web.1]: NoMethodError (undefined method `thumb_image_changed?' for #<Vendor:0x00000005940750>):
2012-12-09T07:00:51+00:00 app[web.1]:
2012-12-09T07:00:51+00:00 app[web.1]: app/controllers/vendors_controller.rb:66:in `block in update'
My VendorsController#update looks normal:
def update
#vendor = Vendor.find(params[:id])
respond_to do |format|
if #vendor.update_attributes(params[:vendor])
format.html { redirect_to #vendor, notice: 'Vendor was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: #vendor.errors, status: :unprocessable_entity }
end
end
end
The offending line is the update_attributes.
This is my views/vendors/_form.html.erb
<%= simple_form_for(#vendor) do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="span8">
<%= f.input :name, :label => "Vendor Name", :wrapper_html => { :class => "span6" } %>
</div>
<div class="span8">
<%= f.input :intro_text, :label => "The headline for your storefront", :as => :text, :wrapper_html => { :class => "span6" }, :input_html => { :rows => 1 } %>
</div>
<div class="span8">
<%= f.input :description, :label => "The description of your store", :as => :text, :wrapper_html => { :class => "span6" }, :input_html => { :rows => 5 } %>
</div>
<div class="span8">
<%= f.input :banner_image, :label => "Upload Banner", :wrapper_html => { :class => "span6" } %>
</div>
<div class="span8">
<%= f.input :logo_image, :label => "Upload Logo", :wrapper_html => { :class => "span6" } %>
</div>
<div class="span8">
<%= f.button :submit, :class => "btn", :wrapper_html => { :class => "span6" } %>
</div>
</div> <!-- /.form-inputs -->
<% end %>
For what it's worth, one of the image sizes in my image_uploader.rb is thumb:
version :thumb, :from_version => :main_banner do
process :resize_to_limit => [170, 120]
end
Thoughts on how I can fix this?
Edit 1
The Vendor model:
class Vendor < ActiveRecord::Base
attr_accessible :name, :description, :banner_image, :logo_image, :intro_text, :thumb_image
mount_uploader :banner_image, ImageUploader
mount_uploader :logo_image, ImageUploader
mount_uploader :thumb_image, ImageUploader
acts_as_tagger
has_many :products
def tags
self.owned_tags
end
def taggings
self.owned_taggings
end
end
Edit 2:
This is what the schema for my Vendor table looks like:
create_table "vendors", :force => true do |t|
t.string "name"
t.text "description", :limit => 255
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.string "banner_image"
t.string "logo_image"
t.string "intro_text"
end
Turns out that I had to remove the call mount_uploader :thumb_image on my Vendor model because I didn't have a column called thumb_image on that model.

Resources