I am using this example for file uploader.
Now it works this way:
I upload a file, after the file is saved,the function(do_picture_analyse) calls R and produces a histogram(simplest version, in the more complicated version 2 packages have to be installed in R), picture of the histogram is saved. The problem is that if I want to upload 50 files, it takes lots of time to load 2 packages in R for each file separately(after_save callback).
What I need:
I upload a file, file is saved, I click on a button "Histogram" and the function do_picture analyses is called on all files that are in the database( It doesnt matter if some of the files have already been analyzed)
So I need only to know how to make an interaction between a button and a call of the function and nothing more.
My show.html.erb:
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-download fade">
<td></td>
<td class="name">
{%=file.name%}
</td>
<td class="nam">
{%=file.name%}
</td>
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
<td class="Pic">
<button class="btn btn-mini btn-info">Pic</button>
</td>
<td class="Hist">
<button class="btn btn-mini btn-primary" >Hist</button>
</td>
<td class="delete">
<button class="btn btn-mini btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
<i class="icon-trash icon-white"></i>
</button>
<input type="checkbox" name="delete" value="1">
</td>
</tr>
{% } %}
</script>
my upload.rb:
def to_jq_upload
{
"name" => (read_attribute(:upload_file_name)).split(".").first,
"size" => read_attribute(:upload_file_size),
"url" => upload.url(:original),
"delete_url" => upload_path(self),
"delete_type" => "DELETE",
"url_chip_image"=>read_attribute(:chip_image),
}
end
after_save :do_picture_analyse
def do_picture_analyse
if read_attribute(:chip_image)==nil
require 'rinruby'
myr = RinRuby.new(echo=false)
myr.filepath=upload.path(:original)
myr.fileurl=upload.url(:original)
myr.eval <<EOF
s=read.table(filepath)
for(j in nchar(filepath):1){
if(substr(filepath,j,j)=="/"){
savepath<-substr(filepath,1,j-1)
file.name<-filepath
file.name<-substr(file.name,j+1,nchar(filepath)-4)
break
}
}
file.name1<-paste(file.name,"image.jpeg",sep="_")
savepath<-paste(savepath,file.name1,sep="/")
jpeg(filename=savepath,width=250, height=250)
hist(s$V1)
dev.off()
EOF
self.update_attributes(
:chip_image => (((myr.fileurl).split("?").first)[6..-5]+'_image.jpeg')
)
end
end
EDIT:
do_picture_analyse can take a folder as a parameter and analyse all files inside it by loading the the packages only one time for entire folder.There are only two folders for the files(two different types of files, let say .txt and .blabla files will be saved either in the txt-Folder or in a blabla-Folder. The type of the folder is saved in the database as well. By clicking the button, two folders should be passed to the do_picture_analyse and it will do everything
Thanks in advance
you need to create a new route for this :
resources :name_of_your_controller do
# use this if you want a route like resources/:id/analyze (single file)
get :analyze, on: :member
# use this if you want a route like resources/analyze (multiple files)
get :analyze, on: :collection
end
then create a new action on your controller :
def analyze
# for single file analysis do something like this :
#file = File.find( params[:id] )
#file.do_picture_analyse
respond_to do |format|
# render what you need to render, js or html
end
# ... or do something like this for multiple file analysis :
#files = File.where( params[:search] )
#files.each {|f| f.do_picture_analyse )
# etc.
end
you can then link your button to your action :
# single file
<%= link_to "Histogram", analyze_file_path( file ) %>
# multiple files
<%= link_to "Histogram", analyze_files_path( search: your_search_conditions ) %>
PS: if your method needs a lot of processing power (if you use R, i assume that you have complex calculations involved), you should consider to extract it in a Worker to run it as a background task.
edit
response to your comments :
i think you should extract this method and make it a class method, that accepts one or more paths.
Then create a collection route that points to your controller ; in your controller action load the files according to some params and does something like this :
# find the directories to be processed :
paths = #files.map(&:folder_type).uniq
# pass them to your class method :
File.do_picture_analyse(paths)
It is even possible to create a class methods that automatically handles these two steps for all files in a relation :
def self.perform_analysis!
paths = all.map(&:folder_type).uniq # or uniq.pluck(:folder_type) on rails >= 3.2.1
do_picture_analyse(paths)
end
def self.do_picture_analyse( *paths )
# call R from here
end
then you can do :
File.where( your_search_params ).perform_analysis!
The short and sweet answer: in your show.html.erb write:
<td class="Hist">
<%= link_to 'Histogram', histogram_path, ;method => :post, :class => 'btn btn-mini btn-primary" %>
</td>
In your config/routes.rb add the following line
post '/histogram' => 'your-controller#histogram', :as => 'histogram'
THis means that the histogram_path will point to a controller named your-controller and call the action histogram. Please replace those with your names.
And then you should be good to go.
I have taken the liberty to propose a POST action, because I am assuming the action is not idempotent. If it is, you should use a GET.
Hope this helps.
Related
How can I check if a column for a Ruby on Rails column is required? I am building a process where people can import a CSV and match the headers in the file to relevant columns in the model. I can't guarantee that they will match so would rather the user could match them manually.
I'm ideally looking for something like Model.column_names[0].required?
If the column is required I want the user to be able to specify a generic value for all imports so that they don't fail unexpectedly (hence the unused input fields in the demo below).
I can upload the file contents and parse them, and have built a table where the user can select which CSV column matches which model column but would like to warn the use which are required. I am yet to create the actual import element but there are various other questions on SO with that well documented.
I've had a google and cant see anything, maybe I'm using the wrong search terms but any help would be appreciated.
Thanks in advance!
Controller:
require 'csv'
myfile = params[:file]
csv_text = File.read(myfile.path)
#csv = CSV.parse(csv_text, :headers => true)
#table_headers = Patient.column_names
View:
<% #table_headers.each_with_index do |header, table_i| %>
<tr>
<td>
<%= header %>
</td>
<td>
<select id="table_column[<%= table_i %>]"=>
<option value="skip">Skip</option>
<option value="custom">Custom</option>
<% #csv.headers.each_with_index do |column, file_i| %>
<option value="<%= file_i %>"><%= column %></option>
<% end %>
</select>
</td>
<td>
<input>
</td>
<tr>
<% end %>
If I get you right, you can use custom validation that will return specific error (that you want to share for user).
https://guides.rubyonrails.org/active_record_validations.html#performing-custom-validations
Basically you need to override validate! method.
https://apidock.com/rails/ActiveModel/Validations/validate%21
For identification of such record (that is not valid) you can user valid? method.
https://apidock.com/rails/v6.0.0/ActiveModel/Validations/valid%3F
I have a custom Rails form that would look like the following :
<form action="/download/zip" id="multifile" method=POST>
<!-- Here is a react component that makes a loop of every record I have of uploaded files, with a checkbox before each of them, that would look like the following -->
<label><input type="checkbox" value={ this.props.file.path } /> { this.props.file.filename } </label>
<input type="submit" value="Download" />
</form>
I choose not to use form_for since the values I want to submit aren't linked to a Model (But if needed, I can use it).
The external controller is here to create a zip of every file selected. It works if I modify the method to 'get' and if I ask to download everything.
So far, here is how it looks:
class DownloadController < ApplicationController
require 'zip'
def zip
abort #params.inspect # returns 'nil'
zip_tmp = File.new("#{Rails.root}/public/zip-#{Time.now.strftime('%d%m%Y')}.zip", 'w+')
Zip::File.open(zip_tmp.path, Zip::File::CREATE) do |zipfile|
FileDetail.all.each do |file| # This works with route set to get
zipfile.add(file.path.split('/')[-1], '/home/username/DEV/rails-react-project/public' + file.path)
end
end
send_file "#{Rails.root}/public/zip-#{Time.now.strftime('%d%m%Y')}.zip"
end
private
def params
#params
end
end
I correctly get redirected to the controller, however when I check if there are some datas to work on, I get nil back.
What would be the "correct" way to do so?
Thank you in advance
(P.S. I know I'll have some issues with this actual code if it works, but gettings datas through the parameter would be nice to start with)
I see it is already commented the mistake but I would like to edit your code and post as answer as it will helpful to other.
Form :
<form action="/download/zip" id="multifile" method=POST>
<!-- Here is a react component that makes a loop of every record I have of uploaded files, with a checkbox before each of them, that would look like the following -->
<label>
<input type="checkbox" name="my_param_name[]" value={ this.props.file.path } /> { this.props.file.filename }
</label>
<input type="submit" value="Download" />
</form>
Controller :
class DownloadController < ApplicationController
require 'zip'
def zip
# This will return array of files user has selected in form
# You can use this to process further to generate zip
files_list = params[:my_param_name]
suffix = Time.now.strftime('%d%m%Y')
zip_file_name = "#{Rails.root}/public/zip-#{suffix}.zip"
zip_tmp = File.new(zip_file_name, 'w+')
Zip::File.open(zip_tmp.path, Zip::File::CREATE) do |zipfile|
FileDetail.all.each do |file| # This works with route set to get
zipfile.add(file.path.split('/')[-1], '/home/username/DEV/rails-react-project/public' + file.path)
end
end
send_file zip_file_name
end
end
Note : I didn't tested code locally, so might give error. Comment on answer if you face error
Hope this helps!
I use rails_admin to manage database of routers and ports. In ports index page I want to change title in <td> tag to node ip address.
This code I have in rails_admin now:
<td class="node_field belongs_to_association_type" title="cisco-node-pos-156">
<a class="pjax" href="/admin/node/85">
cisco-node-pos-156
</a>
</td>
And I want:
<td class="node_field belongs_to_association_type" title="192.168.1.1">
<a class="pjax" href="/admin/node/85">
cisco-node-pos-156
</a>
</td>
In this case node.name = "cisco-node-pos-156" node.ip = "192.168.1.1"
Illustration:
I copied index.html.haml from rails_admin source code and in this line
%td{class: "#{property.css_class} #{property.type_css_class}", title: strip_tags(value.to_s)}= value
I changed strip_tags(value.to_s) to strip_tags(value.ip.to_s) but error message was
undefined method `ip' for "0":String
here "0" is the ports name.
I would a a custom method to your model:
# in your model
def title_ip
# format stuff here
end
# in rails_admin config
list do
include_all_fields
field :title_ip
end
Or your could this in RA config
field :title do
formatted_value { bindings[:object].format_stuff_here }
end
I would not mess with HAML files.
I have the following section of code from my screen scraping script (in a Rails 3.1 application):
# Add each row to a new call record
page = agent.page.search("table tbody tr").each do |row|
next if (!row.at('td'))
time, source, destination, duration = row.search('td').map{ |td| td.text.strip }
call = Call.find_or_create_by_time(time)
call.update_attributes({:time => time, :source => source, :destination => destination, :duration => duration})
end
This was working but I think a few changes have been made on the remote site (they don't currently have an API).
The new HTML code is as follows:
<tr class='o'>
<td class='checkbox'><input class="bulk-check" id="recordings_13877" name="recordings[13877]" type="checkbox" value="1" /></td>
<td>09 Feb 11:37</td>
<td>Danny McClelland</td>
<td>01772123573</td>
<td>00:00:28</td>
<td></td>
<td class='opt recording'>
<img alt="" class="icon recordings" src="/images/icons/recordings.png?1313703677" title="" />
<img alt="" class="icon recording-remove" src="/images/icons/recording-remove.png?1317304112" title="" />
</td>
</tr>
Since the suspected changes the data is being imported in the wrong fields or being missed completely. Currently the only part of the data I want/need is:
<td>09 Feb 11:37</td>
<td>Danny McClelland</td>
<td>01772123573</td>
<td>00:00:28</td>
Sadly, those rows don't have any unique identifiers though.
Any help/advice is appreciated!
Is there a better way to write the script that is more 'future' proof?
the first td is a checkbox now.
So just change it to:
time, source, destination, duration = row.search('td')[1..5].map{ |td| td.text.strip }
There's really no way to future proof a scraper (unless you're psychic)
How should I initiate a delete action from my view?
Creating a new form-tag for each entity just doesn't seem right :-)
<% foreach (var subscriber in group.Subscribers) { %>
<tr>
<td><%= subscriber.Email %></td>
<td><%= Html.ActionLink("[edit]", "edit", "subscriber", new {id=subscriber.SubscriberId}, null) %></td>
<td>
<form id="delete-subscriber-form" method="post" action="<%= Url.Action( "delete", "subscriber", new { #subscriberId = subscriber.SubscriberId }) %>">
<input type="submit" value="Delete" />
</form>
</td>
</tr>
<% } %>
How would you do it?
I normally use checkboxes on the side of the items. Then I can have action links (buttons, whatever) that apply an action to the selected items (such as delete).
you can use CSS and Javascript , add 'forDel' css class for all the elments you want to delete , if you are going to use jquery you can do it like this:
$(".element").each(function(){
$(this).data("id","the id of the element in db")
});
$(".element").toggle(function(){
$(this).addClass("forDel");
},function(){
$(this).removeClass("forDel");
});
and then on pressing the delete button:
var idsForDel;
$(".forDel").each(function(){
idsForDel = $(this).data("id"); + ";";
})
you can pass the idsForDel to the Controller ... and split it in the server side.
It depends on the situation, if you are doing CRUD operations you would normally go with one <form> tag per operation (delete,edit,new). If, however, you are displaying a list and you want to be able to 'multiple delete' items with one click then you will have to approach it from a different angle as you need to encapsulate all the required information into one form.
EDIT
Having had another look at your post above I notice you are providing a 'Delete Button' against each element in the list. For atomic actions like this (i.e. the user expects something to happen straight after they have clicked a button) I would definitely use one form per item.
What I wrote above still applies...