Use rubyzip to download paperclip attachments within nested model - ruby-on-rails

I have the following model setup:
assignments belong to a user and assignments have many submissions
submissions belong to users and also belong to assignments
submissions have attached files (using paperclip).
I want the assignment user (creator) to be able to download the files (submissions) that belong to the particular assignment.
My routes are structured as follows:
resources :assignments do
resources :submissions
end
So, I think I need to define a download action in my assignments controller, which creates a zip archive of all the submissions belonging to an assignment and then redirects directly to that file url for a download.
def download
#submissions = #assignment.submissions.all
input_filenames = #submissions.file_file_name.all
Zip::File.open(assignment.file.url+"/archive.zip", Zip::File::CREATE) do |zipfile|
input_filenames.each do |filename|
zipfile.add(filename, assignment_submission_file_path(#assignment.id, #submission.id)+ '/' + filename)
end
end
respond_to do |format|
format.html { redirect_to assignment.file.url }
format.json { head :no_content }
end
end
Then in my assignment show page, I have the following:
<%= link_to 'Download', #assignment.file.url(:original, false) %>
But when clicked I get an error returning that the file is missing:
No route matches [GET] "/files/original/missing.png"
So the zip archive file is not being created, and thus my routing to the file doesn't work. It's possible I've done something wrong that is very basic, or that the whole thing needs to be structured differently.
Or my other thought was: do I need to create an empty zip archive in the create action of the assignment controller, so that there is an empty zip archive with a viable path to refer to when I want to add stuff into it? If so, how can I do that with the rubyzip gem?
Thanks!

Here's the answer to my own questions:
create an action in the controller called download and then refer to it properly in the show page:
def download
#assignment = Assignment.find(params[:assignment])
#submissions = #assignment.submissions.all
file = #assignment.file.url(:original, false)
Zip::ZipFile.open(file, create=nil) do |zipfile|
#submissions.each do |filename|
zipfile.add(filename.file_file_name, filename.file.url(:original, false))
end
end
And this is the call to that download action in the show page:
<%= link_to "Download", {:controller => "assignments", :action => "download", :assignment => #assignment.id }%>

Related

Rails: How to review content before submit/save?

I know it is simple but I can't get my head around a solution.
It is a job board site. Lets say it's functionality similar to this site. When a user fill all required information and click "To next step" or "Preview", another page loads with all filled data. That page is similar to the final page when data is saved.
When user on preview page, it can go forward and submit the page (in this case it will be saved to DB). Or, click back to Edit the job.
I tried the following::
Within _form.html.erb I added a preview button
<%= f.submit "Preview", :name => 'preview' %>
Within JobControllers I altered create method
def create
if params[:preview]
#job = Job.new(jobs_params)
render 'jobs/preview'
else
#job.save
end
end
Created a Preview view /jobs/preview.html.erb
Now I have 2 problems.
1- Within my preview page, I have an edit button like so: <%= link_to "Edit Job", edit_job_path(#job) %>. But I have an error because I can't find #job. Error says: No route matches {:action=>"edit", :controller=>"jobs", :id=>nil} missing required keys: [:id]
SOLUTION Changed like to <%= link_to 'Back to edit', 'javascript:history.go(-1);' %>
2- How I would submit and add to my DB all information on preview page?
Thank you.
Once I've given a similar task. What I've done is to save records, but not to publish. In my index (resource listing) action of relevant controller, I only fetch published records. Also show action prechecks if that record's published attribute is set to true.
What was my model/controllers looked like before
#model
class Book < ActiveRecord::Base
...
scope :active, -> { where(published: true).some_other_queries }
self.active?
(published && some_other_requirements)
end
...
end
#controller
def index
#books = Book.active
...
end
def show
if #book.active?
render 'show'
...
else
...
end
end
First added a secret key for previews.
#model
def secret
#some custom random key generation
# e.g. Digest::MD5.hexdigest("#{id}_#{ENV['RAILS_SECRET']}")
end
Then added preview action to controller
def preview
# i don't check if the record is active.
# also added a security layer, to prevent irrelevant guys to view
# that record
if #book.secret == params[:secret]
render 'show'
else
...
end
end
In dashboard
...
= link_to "Preview", preview_book_path(book, secret: book.secret)
...
then added a member route
#routes
resources :books do
get :preview, on: :member
end
When I have to do something like this what I normally do is create a review table in my app. This table looks just like the table that is going to saving to.
When they press the "Approved" or "Save" button just populate the new table with the proper data.
I like to create a routes to handle this
resources :something do
match 'move_to_something_else' => 'somethings#move_to_something_else', as: :move_to_something_else, via: :all
end
Now on the controller we can do the following:
def move_to_something_else
#something = Something.find(params[:id])
#something_else = SomethingElse.new
#something_else.name = #something.name
....
#something_else.save
redirect_to something_else_path(#something_else)
end
Alternative you could add a state to your table with the default value of 'draft'
# config/routes.rb
resources :something do
match 'published' => 'somethings#published', as: :published, via: :all
end
# Controller
def published
#something = Something.find(params[:id])
#something.state = 'published'
#something.save
redirect_to something_path(#something)
end

Rails allow user to set default

I am working with a Rails application that allows users to create projects. Inside these projects, users can make lists. I am trying to figure out how to allow users to choose their "Default Working Project" from the projects index page. This would then propagate throughout the app, showing only lists associated with the current project. What is the best approach to making something like this.
You can achieve this easily by adding default_working_project_id field to your users table.
Then in your controller index set:
#default_working_project = current_user.default_working_project
In your user model add:
belongs_to :default_working_project, class_name: Project, foreign_key: :default_working_project_id
You can create your own action. In your routes file:
resources :projects do
member do
get 'set_default'
end
end
In your projects_controller:
def set_default
project.find params[:id]
current_user.default_working_project_id = project.id
respond_to do |format|
if current_user.save
format.html { redirect_to projects_path }
else
format.html { render 'index', notice: "your error message" }
end
end
end
In your views just add
link_to 'set default', set_default_project_path(project.id)
UPDATED
To remove current default project id from user:
You can make some methods to achieve this, like:
In your Project model
If you have a relation that project belongs_to user try this.
def is_a_current_project?
self.id == self.user.default_working_project_id
end
Then create an after_destroy :remove_current_project_relation callback method.
And the method, I recommend to add it inside your private methods:
def remove_current_project_relation
if is_a_current_project?
self.user.default_working_project = nil
end
end
Create a database field in Users table with default_project_id and set it.
On the model set:
def default_project
return projects.find_by_id(default_project_id) if default_project_id
false
end
And then, you can use something like this:
lists = user.default_project ? user.default_project.lists : user.lists
If only one user can see his projects and other users won't be able to see other user's projects, my suggestion is:
Make a boolean value is_default in the projects table. Add it with a migration.
Add :is_default to def project_params in the controller.
In the projects index page use:
<%= render #projects %>
Create file _project.html.erb in views/projects folder, add to it:
<%= form_for project, remote: true do |f| %>
# some project data
<%= f.check_box :is_default, class: 'project_default' %>
<% end %>
In projects_controller:
def update
#project = Project.find(params[:id])
if #project.update_attributes(project_params)
respond_to do |format|
format.html { redirect_to project_page } # this will run when you update project from edit page in form without 'remote: true'
format.js # this will run if you update project with 'remote: true' form
end
end
end
In projects.coffee in assets/javascripts folder:
$('input.project_default').change -> $(this).closest('form').submit()
Create update.js.erb in the views/projects folder, add to it:
$('#edit_project_<%= #project.id %>').replaceWith("<%= j render 'project' %>");
In projects_helper
def current_project
current_user.projects.find_by(is_default: true)
end
Maybe you'll need to change these a little, based on your tasks. This solution will update projects through JavaScript.
Also it would be great to add a method in the Project model, which will make the previous default project not default when the user makes other project default and so on.
When you need to use lists from default project you can use default_project.lists in your views.

Form_for is giving me a No Method Error when trying to create a new object

So as it stands I have a form partial which starts off as:
<%= form_for(#merchandise) do |f| %>
It works perfectly for editing the data that I have already assigned in the rails console. When I try to use it for a "new" form that creates new merchandise (in this case the singular form of merchandise, I don't have nested resources, multiple models etc.), I get a no Method error that states
"undefined method 'merchandises_path' for #<#<Class:0x64eaef0>:0x95d2370>".
When I explicitly state the URL in the form_for method:
<%= form_for(#merchandise url:new_merchandise_path) do |f| %>
I get it to open and I finally have access to the form, however in this case I get a routing error that states
"No route matches [POST] "merchandise/new"".
I decided to write out that route in my routes file which previously just had:
root "merchandise#index" resources :merchandise
After I add the route it literally does nothing. I click submit and it takes me to the form but there is no new data in the database. I am at a complete loss and have been at this for hours googling and stack overflowing and I just don't know what to do anymore. All help is seriously appreciated. I'm adding a pastebin with all my code in the following url:
http://pastebin.com/HDJdTMDt
I have two options for you to fix it:
Option 1:
Please try to do this for best practice in Rails:
routes.rb
change your routes use plural
resources :merchandises
merchandises_controller.rb
Rename your file controller and class into MerchandisesController
class MerchandisesController < ApplicationController
def index
#merchandise = Merchandise.all
end
def new
#merchandise = Merchandise.new
end
def create
#merchandise = Merchandise.new(merchandise_params)
if #merchandise.save
redirect_to merchandises_path
else
render :new
end
end
def show
#merchandise = Merchandise.find(params[:id])
end
def edit
#merchandise = Merchandise.find(params[:id])
end
def update
#merchandise = Merchandise.find(params[:id])
if #merchandise.update(merchandise_params)
redirect_to #merchandise, notice: "The movie was updated"
else
render :edit
end
end
def merchandise_params
params.require(:merchandise).permit(:shipper, :cosignee, :country_arrival_date, :warehouse_arrival_date, :carrier, :tracking_number, :pieces, :palets, :width, :height, :length, :weight, :description, :cargo_location, :tally_number, :customs_ref_number, :released_date, :updated_by, :country_shipped_to, :country_shipped_from)
end
end
Option 2:
If you want to not change many code
/merchandise/_form.html.erb
in partial file
/merchandise/new.html.erb
<%= render 'form', url: merchandise_path, method: 'post' %>
/merchandise/edit.html.erb
<%= render 'form', url: category_path(#merchendise.id), method: 'put' %>

How do I add different types of GET routes that require parameters in Ruby on Rails

I have a list of users being displayed, you can click on "Show user" or "PDF" to see details of that user in HTML or as a PDF document. The show was automatically created with scaffolding, now I'm trying to add the option to view it as a PDF. The problem is adding a second GET option, if I pass the user along as a parameter, it is assumed to be a POST and I get an error that the POST route does not exist. I am not trying to update the user, just to show it in a different way, basically to add a second "show user" option.
How do I tell it that I want a GET, not a POST? Is there an easier way to do what I am trying to do? Thanks.
Please, create a controller like this:
class ClientsController < ApplicationController
# The user can request to receive this resource as HTML or PDF.
def show
#client = Client.find(params[:id])
respond_to do |format|
format.html
format.pdf { render pdf: generate_pdf(#client) }
end
end
end
Please, update route.rb file, action name with post and get, like below :
match 'action_name', to: 'controller#action', via: 'post'
match 'action_name', to: 'controller#action', via: 'get'
More info please read this link : "http://guides.rubyonrails.org/routing.html"
you haven't posted any code or details, so I am guessing you want something like this:
routes
resources :users
controller
class UsersController < ActionController::Base
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # show.html.erb
format.pdf # handle the pdf response
end
end
end
view file in views/users/show.pdf.prawn
prawn_document() do |pdf|
#user.each {|r| pdf.text r.id} # will print user id of the user
end
The way above example will work is, if something visits the following URLs, they will get html file:
localhost:3000/users/1 #html is the default format in rails
localhost:3000/users/1.html
but if they visit .pdf, they will be served a pdf format.
localhost:3000/users/1.pdf
If the above assumptions are correct, then check prawn or wicked_pdf pdf gem. the above example uses prawn
Checkout this link http://apidock.com/rails/ActionController/MimeResponds/InstanceMethods/respond_to. You can add a new MIME type and pass on the :format as pdf in all your rails routes.
Hope this will help.
And for the POST-request check your
config/routes.rb
There shoud be a few routes already, so you can infer the route you need.
In your link you can pass an additional parameter called format for pdf. For e.g.
<%= link_to 'Display in PDF', "/user/pdf", :format => "pdf" %>

How to parse the contents of a uploaded file in RoR

I am new to Rails.
In my project where users have to upload a file, I store it
then I have to parse the file contents and show it in new form.
I have successfully done the file uploading portion,
now how should I read the contents of it?
Try something like this:
upload = params[:your_upload_form_element]
content = upload.is_a?(StringIO) ? upload.read : File.read(upload.local_path)
Very small files can be passed as strings instead of uploaded files, therefore you should check for that and handle it accordingly.
You can open files and read their contents in Ruby using the File class, as this simple example demonstrates:
# Open a file in read-only mode and print each line to the console
file = File.open('afile.txt', 'r') do |f|
f.each do |line|
puts line
end
end
Complete Example
Take, for example, uploading an import file containing contacts. You don't need to store this import file, just process it and discard it.
Routes
routes.rb
resources :contacts do
collection do
get 'import/new', to: :new_import # import_new_contacts_path
post :import, on: :collection # import_contacts_path
end
end
Form
views/contacts/new_import.html.erb
<%= form_for #contacts, url: import_contacts_path, html: { multipart: true } do |f| %>
<%= f.file_field :import_file %>
<% end %>
Controller
controllers/contacts_controller.rb
def new_import
end
def import
begin
Contact.import( params[:contacts][:import_file] )
flash[:success] = "<strong>Contacts Imported!</strong>"
redirect_to contacts_path
rescue => exception
flash[:error] = "There was a problem importing that contacts file.<br>
<strong>#{exception.message}</strong><br>"
redirect_to import_new_contacts_path
end
end
Contact Model
models/contact.rb
def import import_file
File.foreach( import_file.path ).with_index do |line, index|
# Process each line.
# For any errors just raise an error with a message like this:
# raise "There is a duplicate in row #{index + 1}."
# And your controller will redirect the user and show a flash message.
end
end
Hope that helps others!
JP

Resources