Rails4: To check for no image uploaded with carrie

2019-07-23 06:03发布

问题:

What I want to do is for users to select at least one image (up to 3 images) and to enter f.text_area :content in \views\shared\ _article_form.html.erb.
I added a custom validation check_for_at_least_image in \models\article.rb.
It works (error message is displayed) only in create action, but it doesn't work in update action.
How can I check no image and display error message in update action.

article has many photo.

The logs are as followings.

\log/development.log

no image when new create (error message was displayed as I expect)

Started POST "/articles" for 127.0.0.1 at 2014-09-13 10:40:49 +0900
Processing by ArticlesController#create as HTML
  Parameters: {"utf8"=>"笨・, "authenticity_token"=>"xxxx=", "article"=>{"category_id"=>"1379", "photos_attributes"=>{"0"=>{"article_id"=>""}, "1"=>{"article_id"=>""}, "2"=>{"article_id"=>""}}, "content"=>"test"}, "commit"=>"逋サ骭イ縺吶k"}
  [1m[35mUser Load (0.0ms)[0m  SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'xxxx' LIMIT 1
  [1m[36m (0.0ms)[0m  [1mbegin transaction[0m
  [1m[35m (0.0ms)[0m  rollback transaction
  [1m[36mCategory Load (1.0ms)[0m  [1mSELECT "categories".* FROM "categories" WHERE "categories"."id" = ? LIMIT 1[0m  [["id", 1379]]
  Rendered shared/_error_messages.html.erb (1.0ms)
  Rendered shared/_article_form.html.erb (8.0ms)
  Rendered articles/new.html.erb within layouts/application (10.0ms)
  Rendered layouts/_header.html.erb (1.0ms)
  Rendered layouts/_footer.html.erb (0.0ms)
Completed 200 OK in 63ms (Views: 51.0ms | ActiveRecord: 1.0ms)

delete all(three) images (no error message was displayed)

Started PATCH "/articles/40" for 127.0.0.1 at 2014-09-13 11:10:00 +0900
Processing by ArticlesController#update as HTML
  Parameters: {"utf8"=>"笨・, "authenticity_token"=>"xxxx=", "article"=>{"category_id"=>"1379", "photos_attributes"=>{"0"=>{"article_id"=>"40", "_destroy"=>"1", "id"=>"132"}, "1"=>{"article_id"=>"40", "_destroy"=>"1", "id"=>"133"}, "2"=>{"article_id"=>"40", "_destroy"=>"1", "id"=>"134"}}, "content"=>"test"}, "commit"=>"譖エ譁ー縺吶k", "id"=>"40"}
  [1m[35mUser Load (0.0ms)[0m  SELECT "users".* FROM "users" WHERE "users"."remember_token" = 'xxxx' LIMIT 1
  [1m[36mArticle Load (0.0ms)[0m  [1mSELECT "articles".* FROM "articles" WHERE "articles"."user_id" = ? AND "articles"."id" = 40 ORDER BY created_at DESC LIMIT 1[0m  [["user_id", 1]]
  [1m[35mArticle Load (1.0ms)[0m  SELECT "articles".* FROM "articles" WHERE "articles"."id" = ? ORDER BY created_at DESC LIMIT 1  [["id", "40"]]
  [1m[36m (0.0ms)[0m  [1mbegin transaction[0m
  [1m[35mPhoto Load (1.0ms)[0m  SELECT "photos".* FROM "photos" WHERE "photos"."article_id" = ? AND "photos"."id" IN (132, 133, 134)  [["article_id", 40]]
  [1m[36m (0.0ms)[0m  [1mSELECT COUNT(*) FROM "photos" WHERE "photos"."article_id" = ?[0m  [["article_id", 40]]
  [1m[35mSQL (1.0ms)[0m  DELETE FROM "photos" WHERE "photos"."id" = ?  [["id", 132]]
  [1m[36mSQL (0.0ms)[0m  [1mDELETE FROM "photos" WHERE "photos"."id" = ?[0m  [["id", 133]]
  [1m[35mSQL (0.0ms)[0m  DELETE FROM "photos" WHERE "photos"."id" = ?  [["id", 134]]
  [1m[36m (4.0ms)[0m  [1mcommit transaction[0m
Redirected to http://localhost:3000/users/1
Completed 302 Found in 26ms (ActiveRecord: 7.0ms)

photos table

sqlite> .schema photos
CREATE TABLE "photos" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "articl_id" integer, "image" varchar(255), "created_at" datetime, "updated_at" datetim);

\models\article.rb

# encoding: utf-8
class Article < ActiveRecord::Base
    belongs_to :user
    belongs_to :category
    has_many :photos, dependent: :destroy
    accepts_nested_attributes_for :photos, reject_if: :all_blank, allow_destroy: true
    default_scope -> { order('created_at DESC') }
    validates :content, presence: true, length: { maximum: 140 }
    validates :user_id, presence: true
    validates :category_id, presence: true
    validate :check_for_at_least_image

    def build_images
      (3 - self.photos.size).times {self.photos.build}
    end

    def check_for_at_least_image
      errors.add(:image, "select...") if self.photos.size <= 0
    end

end

\models\photo.rb

class Photo < ActiveRecord::Base
    belongs_to :article
   mount_uploader :image, ImageUploader
end

\view\articles\edit.html.erb

<div class="row">
  <div class="span8">
        <%= render 'shared/article_form' %>
  </div>
</div>

\view\shared\ _article_form.html.erb

<%= form_for(@article) do |f| %>
  <%= render 'shared/error_messages', object: f.object %>
  <div class="field">
    <%= f.hidden_field :category_id %>
    <%= f.fields_for :photos do |p| %>
      <%= p.hidden_field :article_id %>
      <div class="photo">
      <% if p.object.image and p.object.image.file %>
        <%= image_tag p.object.image.thumb.url %>
        <p>article:<%= @article.id %></p>
        <p>photo:<%= p.object.id %></p>
        <%= p.hidden_field :image_cache if p.object.image_cache %>
        <label><%= p.check_box :_destroy %>delete</label>
      <% end %>
      <%= p.file_field :image %>
      </div>
    <% end %>
    <%= f.text_area :content, placeholder: "Enter content..." %>
  </div>
  <%= f.submit class: "btn btn-large btn-primary" %>
<% end %>

\controllers\articles_controller.rb

class ArticlesController < ApplicationController

  before_action :signed_in_user, only: [:create, :destroy]
  before_action :correct_user,   only: [:update, :destroy]
.
.
  def new
    @article = Article.new
    @category  = Category.find(params[:category])
    @article.category_id = @category.id
    3.times { @article.photos.build }
  end

  def create
    @article = current_user.articles.build(article_params)
    if @article.save
      flash[:success] = "article created!"
      redirect_to current_user #root_url
    else
        @article.build_images
       render 'new'
    end
  end
.
.
  def edit
    @article = Article.find(params[:id])
    @article.build_images
  end

  def update
    @article = Article.find(params[:id])
    if @article.update(article_params)
      redirect_to current_user
    else
      render 'edit'
    end
  end

  def destroy
    @article.destroy
    redirect_to root_url
  end

  private

    def article_params
      params.require(:article).permit(:content, :category_id, photos_attributes: [:id, :article_id, :image, :image_cache, :_destroy])
    end
.
.
end

回答1:

Just looked at your question and was thinking that may be your validations are running before the destory call on child objects and searched around a bit and found this post, looks like i was right about validations being run before destory. Just going to post pointer related to your question

The problem here is that accepts_nested_attributes_for call destroy for child objects AFTER validation of the parent object. So the user is able to delete an image. Of course, later, when the user will try to edit an article, he/she will get an error – “Select at least one image.”.

Fix:

accepts_nested_attributes_for :photos, reject_if: proc { |attributes| attributes['image'].blank? } , allow_destroy: true  #as discussed in your other question you have to use proc to solve your update problem

validate :check_for_at_least_image

def check_for_at_least_image
  errors.add(:image, "select...") if photos.reject(&:marked_for_destruction?).size <= 0
end