Rails: Provide param to nested controller - ruby-on-rails

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) { %}
<img src="{%=file.thumbnail_url%}">
{% } %}</td>
<td class="name">
{%=file.name%}
</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

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.

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.

Related

Modal for tr - different id index page

I have an index page with different software in a table.
I want to display additional information (in a modal) when we click on a tr.
Everything works but I have the information of a single software that appears in my modal and is the same for each tr.
I would like to display the information of each software in the corresponding modals.
My script :
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal').modal('show');
}
});
My view :
<% #nonpremium.each do |software| %>
<table>
<tr class="clickable">
<td class="hey1">
<%= link_to software_path(software), class: "no-click" do %>
<%= image_tag software.logo.url(:medium), class:"no-click"%>
<% end %>
</td>
<td class="hey3">
<h6><%= software.slogan %></h6>
<p><%= software.largeslogan %></p>
</td>
</tr>
</table>
<div class="modal fade bd-example-modal-lg" id="exampleModal" tabindex="-1" role="dialog" aria-labelledby="myLargeModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
<button type="button" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<%= link_to software.software_name, software_path(software), class:"no-click" %>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<% end %>
I tried something like this in my script, but it does not work ..
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal-<%= #software.id %>').modal('show');
}
});
Thx for you help
EDIT :
Controller/pages
class PagesController < ApplicationController
before_action :click, only: :index
def home
#softwares = Software.all.order(:cached_votes_up => :desc )
#premium = Software.includes(:user).where(users: { subscribed: true }).order("RANDOM()").limit(2)
#nonpremium = #softwares - #premium
end
def search
#softwares = Software.ransack(name_cont: params[:q]).result(distinct: true)
#categories = Category.ransack(name_cont: params[:q]).result(distinct: true)
respond_to do |format|
format.html {}
format.json {
#softwares = #softwares.limit(5)
#categories = #categories.limit(5)
}
end
end
end
EDIT 2 :
I have the desired result by putting in my table the information that I want to recover, then I put a "display: none".
<style>
td.test {
display:none;
}
</style>
<td class="test">
<span><%= software.software_description %></span>
<span><%= get_video_iframe(software.youtube_id) %></span>
</td>
Then I get the information from my table in my script:
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
var description = this.childNodes[3].innerHTML;
var name = this.childNodes[5].innerHTML;
document.getElementById("myModalName").innerHTML = name;
document.getElementById("myModalDesc").innerHTML = description;
$('#exampleModal').modal('show');
}
});
For then displayed in my modal:
...
<div class="modal-body" id="myModalName">
Name
</div>
<div class="modal-body" id="myModalDesc">
Description
</div>
...
There is probably better to do, but being a beginner is how I achieve the desired result.
However I would like to post videos in my modals.
Am I not going to overload my home page by hiding youtube videos with my display: none?
You won't be able to use erb in your script (unless this is in a script tag within your view, in which case your code should work) - better using a data attribute. For example, if you update your tr to the following:
<%= content_tag :tr, class: "clickable", data: { software_id: #software.id } do %>
# the rest of your code within the tr
<% end %>
# Equivalent of using:
# <tr class="clickable" data-software_id="<%= #software.id %>">
This attaches the relevant software_id to the tr in the DOM. You can then use the following in your script, accessing this new attribute:
$(".clickable").click(function(e) {
if (!$(e.target).hasClass('no-click')) {
$('#exampleModal-' + $(e.target).data('software_id')).modal('show');
}
});
And everything should work as desired.
Let me know how you get on or if you have any questions. Hope this helps!
Edit based on your comment:
That error you're seeing will come because #software is nil and you are, therefore, attempting to call id on nil.
It's a common error, and means to need to ensure #software is correctly set in your controller. If you post your controller code, I might be able to help with this.
Alternatively, you can 'safely' try the method, using #software&.id with newer versions of Ruby / Rails (or #software.try(:id) on older versions). However, that's not likely to be helpful here, more of a side note :)
Edit 2:
So, in your controller, you're not actually assigning the singular #software, rather the plural #softwares:
#softwares = Software.all.order(:cached_votes_up => :desc )
#premium = Software.includes(:user).where(users: { subscribed: true }).order("RANDOM()").limit(2)
#nonpremium = #softwares - #premium
Then, in your view, you're looping through #nonpremium using the local variable software. So, you can either:
assign #software in the controller if it should always use the same data in the modal
go back to the previous option, assigning a data attribute to the tr, which is what I'd recommend. Using that should work, although you'll need to alter the code to use software without the # to address the correct variable.
I.E.
<%= content_tag :tr, class: "clickable", data: { software_id: software.id } do %>
# the rest of your code within the tr
<% end %>
This ensures the script addresses the click based on the element clicked, and pulls the id directly from there, which is within the scope of your software loop.
That do it for ya?

Rails, rendering after destroy

I have a Cart/show.html erb with that renders a partial called carts_row.html.erb
<p id="notice"><%= notice %></p>
</br>
</br>
<div class="cart-container">
<%=render 'carts_row'%>
</div>
</p>
In carts_row it's a shopping cart layout with forms to update quantity and destroy/remove item from your shopping cart.
<h1>Shopping Cart</h1>
<div class="shopping-cart">
<div class="column-labels">
<label class="product-image">Image</label>
<label class="product-details">Product</label>
<label class="product-price">Price</label>
<label class="product-quantity">Quantity</label>
<!-- adding update column to labels -->
<label class="product-update">Update</label>
<label class="product-removal">Remove</label>
<label class="product-line-price">Total</label>
</div>
<%if #cart.cart_items.size == 0%>
<div class="text-center">
<p>Looks like you have nothing in your cart! <%=link_to 'Add something to your cart', products_path%></p>
</div>
<%end%>
<br />
<br />
<%if #cart_items != nil%>
<%#cart_items.each do |item|%>
<div class="product">
<div class="product-image">
<%=image_tag(item.product.picture)%>
</div>
<div class="product-details">
<div class="product-title"><%=item.product.name%></div>
<p class="product-description"><%=item.product.description%></p>
</div>
<div class="product-price"><%=number_to_currency(item.product.price)%></div>
<%=form_for item, remote: true do |f|%>
<div class="product-quantity">
<%=f.number_field :quantity, value: item.quantity, min: 1, max: 8%>
<%=f.hidden_field :product_id, value: item.product.id%>
<!-- <input type="number" value="<%=item.quantity%>" min="1" max="8"> -->
</div>
<div class="product-update">
<%=f.submit 'Update', class: "update-product"%>
<!-- <%=f.hidden_field :product_id, value: item.product.id%> -->
<!-- <button class="update-product">
Update
</button> -->
</div>
<div class="product-removal">
<%= link_to "Remove", item, {data: {confirm: "Are you sure you wish to delete the product '#{item.product.name}' from your cart?"}, method: :delete, remote: true, class: "remove-product"}%>
<!-- <button class="remove-product">
Remove
</button> -->
<%end%>
</div>
<div class="product-line-price"><%=number_to_currency(item.product.price*item.quantity)%></div>
</div>
<%end%>
<%end%>
<br />
<br />
<!--Temporary solution until model methods are written -->
<% if #cart_items != nil%>
<!--subtotal variable -->
<%subtotal =#cart_items.collect {|item| item.quantity*item.unit_price}.sum%>
<!--subtotal + shipping cost -->
<%total=subtotal+15%>
<%end%>
<div class="totals">
<div class="totals-item">
<label>Subtotal</label>
<div class="totals-value" id="cart-subtotal"><%=subtotal%></div>
</div>
<div class="totals-item">
<label>Tax (5%)</label>
<div class="totals-value" id="cart-tax">Included!</div>
</div>
<div class="totals-item">
<label>Shipping</label>
<div class="totals-value" id="cart-shipping">15.00</div>
</div>
<div class="totals-item totals-item-total">
<label>Grand Total</label>
<div class="totals-value" id="cart-total">
<%=total%>
</div></div>
</div>
</div>
<div class="checkout-btn">
<button class="checkout">Checkout</button>
</div>
</div>
The update in the form goes to update.js.erb which has this
<% if #cart.errors.any? || #cart_item.errors.any? %>
alert("Not valid.");
<%else%>
$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text')%>")
$(".cart-container").html("<%= escape_javascript(render 'carts/carts_row')%>")
<%end%>
And destroy in the form has a same destroy.js.erb
<% if #cart.errors.any? || #cart_item.errors.any? %>
alert("Not valid.");
<%else%>
$(".cart-text").html("<%= escape_javascript(render 'layouts/cart_text')%>")
$(".cart-container").html("<%= escape_javascript(render 'carts/carts_row')%>")
<%end%>
The update renders the carts_row.html.erb form properly.
When I destroy an item it renders the page with empty products and a checkout total at the bottom. When I refresh the page the correct amount of products in the shopping cart is displayed and everything is where it should be!
How is the render of update correct but render of destroy incorrect???
My carts_item controller has these two methods for the forms:
def update
#cart = current_cart
# finding cart_items by cart_id
#cart_item = #cart.cart_items.find(params[:id])
# #cart_items.order(:id)
#cart_item.update_attributes(cart_item_params)
#cart_items = #cart.cart_items.order(:id)
# redirect 'cart_show_path'
end
def destroy
#cart = current_cart
#cart_item = #cart.cart_items.find(params[:id])
#cart_item.destroy
#cart_items = #cart.cart_items
end
You're destroying the item from cart, but not removing it from #cart.cart_items
Try doing this
def destroy
#cart = current_cart
#cart_item = #cart.cart_items.find(params[:id])
#Find the index of #cart_item
item_index = #cart.cart_items.index(#cart_item)
#Remove it from #cart.cart_items when its destroyed successfully
if #cart_item.destroy
#cart.cart_items.delete_at(item_index)
end
#cart_items = #cart.cart_items
end
Let me know if it works!
I figured out the problem. The problem was that I was using a <% if #cart_items != nil %> check in my view.
This always returns false, because ActiveRecord will always return an array!!!!!!!!!! Whether it has product objects in the #cart_items object or not.
After I changed this to test for empty arrays with #cart_items.empty?, then the page worked like I wanted it to.
Thanks kumar for your input.

Render a Rails view in a Bootstrap modal

I have hit a brick wall. I've been hacking at this problem for so long now, I'm not even sure how I got to where I am. All I can say is, I've tried all of the below without success:
How to add bootstrap modal with link_to so the link content open in modal ?
How to show twitter bootstrap modal via JS request in rails?
rails link_to :remote
http://apidock.com/rails/ActionView/Helpers/PrototypeHelper/link_to_remote
How do I render "new", "edit" and "delete" views within Bootstrap modals on the "index" view rather than linking to separate pages for each?
Here is my code as it stands now. For now, lets ignore "edit" and "delete" and just focus on "new". When I click the "New" button, a modal with the string "<%= j render "items/new" %>" appears (instead of the form that that ruby statement should render). What am I doing wrong?:
items_controller.rb:
class ItemsController < ApplicationController
def index
#items = Item.all
end
def new
respond_to do |format|
format.js {}
end
end
def create
#item = Item.new(item_params)
if #item.save
flash[:notice] = "'#{#item.name}' saved!"
redirect_to items_path
else
flash[:notice] = "Something went wrong :("
render "index"
end
end
def edit
#item = Item.find(params[:id])
respond_to do |format|
format.js {}
end
end
def update
#item = Item.find(item_params[:id])
if #item.update_attributes(item_params)
flash[:notice] = "Successfully updated #{#item.name}."
redirect_to items_path
else
flash[:notice] = "Oops"
# render "edit"
end
end
private
def item_params
params.require(:item).permit(:name, :bid, :uuid)
end
end
items/index.html.erb
<div class="row">
<div class="col-xs-12">
<%= link_to "New", new_item_path, remote: true, class: "btn btn-success pull-right new", data: { toggle: "modal", target: "#newModal" } %>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<table class="table table-hover items">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>UUID</th>
<th colspan="2">Links</th>
</tr>
</thead>
<tbody>
<% #items.each do |item| %>
<tr>
<td><%= item.id %></td>
<td><%= item.name %>
<!-- edit/remove icons -->
<span class="edit-remove">
<%= link_to edit_item_path(item.id), remote: true, data: { toggle: "modal", target: "#editModal" } do %>
<span class="glyphicon glyphicon-pencil text-muted"></span>
<% end %>
<a href="#">
<span class="glyphicon glyphicon-remove text-muted"></span>
</a>
</span>
</td>
<td><%= item.uuid %></td>
<td><%= link_to "XXX", "http://xxx" %></td>
<td><%= link_to "XXXX", "http://xxx", target: "_blank" %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
</div>
<!-- newModal skeleton -->
<div class="modal fade" id="newModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
<!-- editModal skeleton -->
<div class="modal fade" id="editModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
<!-- deleteModal skeleton -->
<div class="modal fade" id="deleteModal">
<div class="modal-dialog">
<div class="modal-content">
</div>
</div>
</div>
items/new.html.erb
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
<h4 class="modal-title">New Item</h4>
</div>
<div class="modal-body">
<%= form_for :item, url: { action: "create" } do |f| %>
<div class="form-group">
<%= f.label :name, "Name" %>
<%= f.text_field :name, { class: "form-control" } %>
</div>
<div class="form-group">
<%= f.label :bid, "BID" %>
<%= f.text_field :bid, { class: "form-control" } %>
</div>
<div class="form-group">
<%= f.label :uuid, "UUID" %>
<%= f.text_field :uuid, { class: "form-control" } %>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<%= submit_tag "Save", class: "btn btn-primary" %>
<% end %>
</div>
javascripts/items.js
$(document).on("page:change", function() {
$("#newModal .modal-content").html('<%= j render "items/new" %>');
});
In the case of new for instance, you want to render a javascript file. For this, you'll need to create items/new.js.erb.
Also, remove ", data: { toggle: "modal", target: "#newModal" }" from your link, we will do that in the javascript.
# new.js.erb
$("#newModal .modal-content").html('<%= j render "items/form" %>');
$("#newModal").modal(); // Or whatever the Bootstrap function is to render the modal
# items/_form.html.slim
# Here you'll put your form
You cannot use "render" on views directly, you should render partials and not views (this is why I asked you to put your form into a partial).
I threw this together and it puts a big '3' in my document 3 seconds after loading it:
<script>
setTimeout(function() {
$("#holder").html("<%= j render(:file => 'things/test.html.erb') %>");
}, 3000);
</script>
<div id="holder></div>
app/views/things/test.html.erb:
<h1><%= 1 + 2 %></h1>
That should get you going.

Rails 4 + Carrierwave + jQuery File Upload + Nested Form Multiple File Issue

I am having a slight issue with a rails app I am working on. I am utilizing Carrierwave, Nested_form, Simple_form, and Jquery-file-upload gems.
The majority of everything is working fine, with the exception of the data.
I have two models, a Project Model and an Attachments Model.
When the Project form is submitted, all the files upload as they should, a record in the attachments model is created as it should (one per file). But as for the projects model, a record is created for each file as well.
I can't seem to figure out (on a single form, with single submit) how to get only one record for the project and multiple records for the attachments.
Any help would be appreciated, I've outlined my code below. I'd like to avoid a two step process if possible, but if someone could point me in the right direction that would help.
Project Model
class Project < ActiveRecord::Base
has_many :attachments, :dependent => :destroy
accepts_nested_attributes_for :attachments, :allow_destroy => true
def generate_token
self.token = loop do
random_token = SecureRandom.urlsafe_base64
break random_token unless Project.where(token: random_token).exists?
end
end
end
Attachment Model
class Attachment < ActiveRecord::Base
belongs_to :project, :polymorphic => true
include Rails.application.routes.url_helpers
mount_uploader :file, AttachmentUploader
def to_jq_upload
{
"name" => read_attribute(file),
"url" => file.url,
"size" => file.size,
"delete_url" => attachment_path(:id => id),
"delete_type" => "DELETE"
}
end
end
Projects Controller
class ProjectsController < ApplicationController
def index
#projects = Project.all
respond_to do |format|
format.html
format.json { render json: #projects }
end
end
def new
#project = Project.new
#project.token = #project.generate_token
#attachments = #project.attachments.build
end
def create
#project = Project.new(project_params)
respond_to do |format|
if #project.save
format.html { redirect_to projects_url, notice: 'Project was successfully created.' }
format.json { render json: #project, status: :created, location: #project }
else
format.html {}
format.json {}
end
end
end
def destroy
#project = Project.find(params[:id])
#project.destroy
respond_to do |format|
format.html { redirect_to projects_url }
format.json { head :no_content }
end
end
private
def project_params
params.require(:project).permit(:name, :token, :number_of_pages, :number_of_copies, :flat_page_size, :trim_page_size, :purchase_order_number, :preferred_delivery_date, :delivery_method, :delivery_instructions, :project_instructions, attachments_attributes: [:id, :attachment, :name, :filename, :file, :project_token, :branch])
end
end
Attachments Controller
class AttachmentsController < ApplicationController
before_filter :the_project
def index
#attachments = Attachment.where("project_id = ?", the_project)
render :json => #attachments.collect { |p| p.to_jq_upload }.to_json
end
def create
#project = Project.find(params[:project_id])
#attachment = Attachment.new(attachment_params)
if #attachment.save
respond_to do |format|
format.html { render :json => [#attachment.to_jq_upload].to_json, :content_type => 'text/html', :layout => false }
format.json { render :json => {files: [#attachment.to_jq_upload]}.to_json }
end
else
render :json => [{ :error => "custom_failure "}], :status => 304
end
end
def destroy
#attachment = Attachment.find(params[:id])
#attachment.destroy
render :json => true
end
private
def the_project
#project = Project.find(params["project_id"])
end
end
New Project Form (app/views/projects/new.html.erb)
<h2>New Project</h2>
<br />
<%= simple_nested_form_for #project, :defaults => { :wrapper_html => {:class => 'form-group'}, :input_html => { :class => 'form-control' } }, :html => { :multipart => true, :id => "fileupload", :class => 'horizontal-form', :role => "form" } do |f| %>
<div class="row">
<div class="col-lg-12">
<%= f.input :name, :label => "Project Name / Description", :class => 'col-lg-12' %>
<%= f.hidden_field :token %>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<%= f.input :number_of_pages %>
<%= f.input :flat_page_size %>
<%= f.input :purchase_order_number %>
</div>
<div class="col-lg-6">
<%= f.input :number_of_copies %>
<%= f.input :trim_page_size, :label => 'Finished Size <em><small>(If Different from Flat Page Size)</small></em>' %>
<%= f.input :preferred_delivery_date, :as => :text %>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<%= f.input :delivery_method %>
</div>
<div class="col-lg-6">
<%= f.input :project_instructions %>
</div>
</div>
<div class="row">
<div class="col-lg-6">
<select id="branches">
<option>Calgary Downtown</option>
<option>Calgary South</option>
<option>Edmonton</option>
<option>Kelowna</option>
</select>
</div>
</div>
<br />
<div class="row fileupload-buttonbar">
<div class="col-lg-7">
<%= fields_for :attachments do |a| %>
<span class="btn btn-success fileinput-button">
<i class="glyphicon glyphicon-plus"></i>
<span>Add files...</span>
<%= a.file_field :file, :name => 'project[attachments_attributes][0][file]', :multiple => true %>
</span>
<button type="submit" class="btn btn-primary start">
<i class="glyphicon glyphicon-upload"></i>
<span>Start Upload</span>
</button>
<button type="reset" class="btn btn-warning cancel">
<i class="glyphicon glyphicon-ban-circle"></i>
<span>Cancel Upload</span>
</button>
<button type="button" class="btn btn-danger delete">
<i class="glyphicon glyphicon-trash"></i>
<span>Delete Upload</span>
</button>
<%= a.hidden_field :branch, :value => "Calgary Downtown" %>
<%= a.hidden_field :project_token, :value => #project.token %>
<% end %>
</div>
<div class="col-lg-5">
<div class="progress progress-success progress-striped active fade">
<div class="bar" style="width:0%"></div>
</div>
</div>
</div>
<div class="row fileupload-loading"></div>
<div class="row">
<table class="table table-striped">
<tbody class="files" data-toggle="modal-gallery" data-target="#modal-gallery">
</tbody>
</table>
</div>
<% end %>
<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) { %}
<img src="{%=file.thumbnail_url%}">
{% } %}</td>
<td class="name">
{%=file.name%}
</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>
<script type="text/javascript" charset="utf-8">
$(function () {
var num_added = 0;
var added = 0;
var all_data = {};
$('#branches').change(function() {
var test = $("option:selected",this).text();
$('#project_attachments_attributes_0_branch').val(test);
});
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
sequentialUploads: true,
});
});
</script>
I'm working on more or less that exact same issue (mine involves stock_items - not projects, but anyways...)
One issue is the #fileupload.fileupload which initializes the entire form - and that will post your project, which you will not want to
I've not solved that one yet - but somehow 'we' have to make the parent form not POST
I believe you need to include the index of the 'parent' instance in the input, in this case, Project. So:
<%= a.file_field :file, :name => 'project[*index*][attachments_attributes][0][file]', :multiple => true %>

Coffeescript rails select one of two models

I have two models in ruby,
#photos = Photo.all
#comments = Comment.all
#objects = #photos + #comments
I have in my html a button id"only-photo" and a button "only-comments", if i click on the first I want to select only the photos.
how can I do to only select #photos from #objects? in coffeescript
here is what i tried
<div id="list">
<% #objects.each do |l| %>
<% if l.class == "Photo"%>
<div id="photo" class="photo">
<%= render "shared/photo", :photo => l %>
</div>
...
</div>
<div id="filters">
<button id="only-photo">
</button>
...
</div>
then in Coffeescript I just do:
$("#filters").on "click", "#only-photo", ->
$("#list #comment").removeClass "active"
$("#list #photo").addClass "active"
BUT I am sure there is a more elegant way to do these things!
Can't we filter on a scope from coffeescript? or anything like this?
Thank you!
Try this:
<div id="list">
<% #objects.each do |l| %>
<div id="<%= l.class.to_s.downcase %>_<%= l.id %>" class="<%= l.class.to_s.downcase =%>">
<%= render "shared/#{l.class.to_s.downcase}", :photo => l %>
</div>
<% end %>
</div>
<div id="filters">
<button id="only-photo" class="filter" value="photo">
</button>
<button id="only-comment" class="filter" value="comment">
</button>
</button>
</div>
Coffee:
$("button.filter").on "click", ->
class_to_hide = $(this).attr('value')
$("."+class_to_hide).toggleClass('active')

Resources