Question

He

I have two models in my rails application (Post, Picture) that are associated as follows:

#Post model
has_many :pictures, :dependent => :destroy 
accepts_nested_attributes_for :pictures, :allow_destroy => true 

#Picture model
belongs_to :post

In my post edit view I have two forms, so I can edit the post content as well as add pictures to the post. I use the jquery file upload plugin together with carrierwave to handle the upload process. This looks quite similar to this setup here http://tinyurl.com/aun7bl5

When I go to the post edit view the jquery file upload always shows me all pictures, because it uses the index action of the picture controller which fetches all images and renders them to json so jquery file upload can handle them. The index action looks like this.

def index
  @pictures = Picture.all 
  render :json => @pictures.collect { |p| p.to_jq_upload }.to_json
end

The post param (:id) is available to the post controller when I edit a post. I can see it with the logger. But it is not available to the index action which is nested inside of the post edit form.

Now my question is, how I can provide the index action in the post controller with the id of the post I like to edit so that I can do there something like this to filter the pictures it gets:

def index
  @pictures = Picture.where(:post_id => params[:id])
  render :json => @pictures.collect { |p| p.to_jq_upload }.to_json
end

EDIT:

#Post#edit view
<div class=post-well>
  <div class="page-header">
    <h1>Reisebericht editieren</h2>
  </div>

  <%= simple_form_for @post do |f| %>
    <%= f.error_notification %>
    <div class="form-inputs">
      <%= f.input :title, :label => "Titel", :input_html => { :class => 'new-post-inputfields' } %>
      <%= f.input :body, :label => "Artikel", :input_html => { :class => 'new-post-inputfields' } %>
      <%= f.hidden_field :picture_ids, :input_html => { :id => 'post_picture_ids' } %> 
      <%= f.button :submit, :label => "Speichern" %>
    </div> 
  <% end %>

  <h4>Bilder verwalten</h4>
  <%= simple_form_for Picture.new, :html => { :multipart => true, :id => "fileupload"  } do |f| %>
    <!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
    <div class="row fileupload-buttonbar">
      <div class="span7">
        <!-- The fileinput-button span is used to style the file input field as button -->
        <span class="btn btn-success fileinput-button">
          <i class="icon-plus icon-white"></i>
          <span>Hinzufügen</span>
          <%= f.file_field :path, multiple: true, name: "picture[path]" %>
        </span>
        <button type="submit" class="btn btn-primary start">
          <i class="icon-upload icon-white"></i>
          <span>Upload</span>
        </button>
        <button type="reset" class="btn btn-warning cancel">
          <i class="icon-ban-circle icon-white"></i>
          <span>Abbrechen</span>
        </button>
        <button type="button" class="btn btn-danger delete">
          <i class="icon-trash icon-white"></i>
          <span>Delete</span>
        </button>
        <input type="checkbox" class="toggle">
      </div>
      <div class="span5">
        <!-- The global progress bar -->
        <div class="progress progress-success progress-striped active fade">
          <div class="bar" style="width:0%;"></div>
        </div>
      </div>
    </div>
    <!-- The loading indicator is shown during image processing -->
    <div class="fileupload-loading"></div>
    <br>
    <!-- The table listing the files available for upload/download -->
    <table class="table table-striped"><tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery"></tbody>
    </table>
  <% end %>
</div> 

<script>
  var fileUploadErrors = {
    maxFileSize: 'File is too big',
    minFileSize: 'File is too small',
    acceptFileTypes: 'Filetype not allowed',
    maxNumberOfFiles: 'Max number of files exceeded',
    uploadedBytes: 'Uploaded bytes exceed file size',
    emptyResult: 'Empty file upload result'
  };
</script>

<!-- The template to display files available for upload -->
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-upload fade">
    <td class="preview"><span class="fade"></span></td>
      <td class="name"><span>{%=file.name%}</span></td>
        <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
        {% if (file.error) { %}
          <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
        {% } else if (o.files.valid && !i) { %}
          <td>
            <div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
            </td>
            <td class="start">{% if (!o.options.autoUpload) { %}
              <button class="btn btn-primary">
                <i class="icon-upload icon-white"></i>
                  <span>{%=locale.fileupload.start%}</span>
                </button>
            {% } %}</td>
        {% } else { %}
          <td colspan="2"></td>
        {% } %}
        <td class="cancel">{% if (!i) { %}
          <button class="btn btn-warning">
            <i class="icon-ban-circle icon-white"></i>
              <span>{%=locale.fileupload.cancel%}</span>
            </button>
        {% } %}</td>
    </tr>
{% } %}
</script>
<!-- The template to display files available for download -->
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
  <tr class="template-download fade">
    {% if (file.error) { %}
      <td></td>
        <td class="name"><span>{%=file.name%}</span></td>
          <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
            <td class="error" colspan="2"><span class="label label-important">{%=locale.fileupload.error%}</span> {%=locale.fileupload.errors[file.error] || file.error%}</td>
        {% } else { %}
          <td class="preview">{% if (file.thumbnail_url) { %}
            <a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
            {% } %}</td>
            <td class="name">
              <a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
            </td>
            <td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
            <td colspan="2"></td>
        {% } %}
        <td class="delete">
          <button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
            <i class="icon-trash icon-white"></i>
              <span>{%=locale.fileupload.destroy%}</span>
            </button>
            <input type="checkbox" name="delete" value="1">
        </td>
    </tr>
{% } %}
</script>

And the javascript:

$(function () {  
  // Initialize the jQuery File Upload widget:
  $('#fileupload').fileupload({
  completed: function(e, data) {
     console.log(data.result[0].picture_id); 
     $("#post_picture_ids").val(function(i,val) { 
          return val + (val ? ', ' : '') + data.result[0].picture_id;
     });
   }
 });

 // Load existing files:
 $.getJSON($('#fileupload').prop('action'), function (files) {
  var fu = $('#fileupload').data('fileupload'), 
  template;
  fu._adjustMaxNumberOfFiles(-files.length);
  template = fu._renderDownload(files)
  .appendTo($('#fileupload .files'));
  // Force reflow:
  fu._reflow = fu._transition && template.length &&
  template[0].offsetWidth;
  template.addClass('in');
 $('#loading').remove();
 });
}); 

Any help with this would be appreciated.

EDIT2: For one solution see below under @SybariteManoj answer. Another solution is to use:

$.getJSON($('#fileupload').prop('action') + '/' + $('#current_post_id').val(), function (files) {

in the beginning of the get function and then add a route for the pictures controller as follows:

get 'pictures/:id', to: 'pictures#index'

The index action in the pictures controller will then filter for the id parameter in this solution and looks like this:

def index
  @pictures = Picture.where(:post_id => params[:id])
  render :json => @pictures.collect { |p| p.to_jq_upload }.to_json
end 

I think I prefer the full solution of @SybariteManoj so there is no need for a route and the index action loks like this now.

def index
  @pictures = Picture.where(:post_id => params[:post_id])
  render :json => @pictures.collect { |p| p.to_jq_upload }.to_json
end 
Was it helpful?

Solution

I think I got the culprit. In your javascript, $.getJSON($('#fileupload').prop('action') this is passing the value of the action attribute of the image upload form.

Try adding this line somewhere in your edit view file

<%= hidden_field_tag :current_post_id, @post.id, :id => 'current_post_id' %>

and replace this line

$.getJSON($('#fileupload').prop('action'), function (files) {

with

$.getJSON($('#fileupload').prop('action') + '?post_id=' + $('#current_post_id').val(), function (files) {

I haven't tested it but I am quite sure this should solve your issue.

OTHER TIPS

Since you are editing the post, the post params[:id] is available to the post controller's update action and not to others which is the default action call after editing the form in rails.

If you want the params[:id] in the index action then you need to either redirect to the index action after update action is called or you need to put the logic of showing the selected pictures in the update action only.

You can also create a custom action method to handle the process of showing the pictures that belongs to the post.

I suppose that your Picture has a foreign_key named post_id and you can simply use this in your index action to get only pictures which belong to the Post.

Try something like this :

def index
  @pictures = @post.pictures 
  render :json => @pictures.collect { |p| p.to_jq_upload }.to_json
end

EDIT

Since your pictures belong_to your post, you also need to modify the new and create actions so that you create a picture for your post.

One way to do that is to create a method find_post in your Picture controller, and make a before_filter callback like this :

class PicturesController < ApplicationController
  before_filter :find_post, :only => [:index, :new, :create]

  def find_post
    @post = Post.find(params[:post_id]) unless params[:post_id].nil?
  end

  def new
    @picture = @post.pictures.new
  end

  ## Same thing for the create action
end

And in your view, do the same when you create you form :

<%= simple_form_for @post.pictures.new

Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top