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
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.
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
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"
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)
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.