In Rails how to check if javascript file exists before using javascript_include_tag - ruby-on-rails-3

In a rails 3.2 app, I would like to check for the existence of a javascript file in the asset pipeline before including the javascript_include_tag in a file. Something like:
<% if javascript_file_exists? %>
<%= javascript_include_tag "#{controller_name}_controller" %>
<% end %>
I would like to do this so that the absence of a javascript file will not result in an error. Is there a way to do this?

A more Rails way is to use a helper. This allows you to check for a coffeescript or a javascript version of a file:
def javascript_exists?(script)
script = "#{Rails.root}/app/assets/javascripts/#{params[:controller]}.js"
File.exists?(script) || File.exists?("#{script}.coffee")
end
Then you can use it in your layout:
<%= javascript_include_tag params[:controller], :media => "all" if javascript_exists?(params[:controller]) %>
You can do the same with your CSS:
-- helper --
def stylesheet_exists?(stylesheet)
stylesheet = "#{Rails.root}/app/assets/stylesheets/#{params[:controller]}.css"
File.exists?(stylesheet) || File.exists?("#{stylesheet}.scss")
end
-- layout --
<%= stylesheet_link_tag params[:controller], :media => "all" if stylesheet_exists?(params[:controller]) %>
EDIT: updated #javascript_exists?
I have recently made some changes to my javascript_exists? helper:
def javascript_exists?(script)
script = "#{Rails.root}/app/assets/javascripts/#{script}.js"
extensions = %w(.coffee .erb .coffee.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || File.exists?("#{script}#{extension}")
end
end
call it in the application layout:
<%= javascript_include_tag params[:controller] if javascript_exists?(params[:controller]) %>
This will now handle more extensions and use an inject to determine if the file exists. You can then add a bunch more extensions to the extensions array, as needed for your app.
EDIT DEUX: Updated #stylesheet_exists?
Same, but for stylesheets:
def stylesheet_exists?(stylesheet)
stylesheet = "#{Rails.root}/app/assets/stylesheets/#{stylesheet}.css"
extensions = %w(.scss .erb .scss.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || File.exists?("#{stylesheet}#{extension}")
end
end
EDIT Last (probably): DRY it up
def asset_exists?(subdirectory, filename)
File.exists?(File.join(Rails.root, 'app', 'assets', subdirectory, filename))
end
def image_exists?(image)
asset_exists?('images', image)
end
def javascript_exists?(script)
extensions = %w(.coffee .erb .coffee.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || asset_exists?('javascripts', "#{script}.js#{extension}")
end
end
def stylesheet_exists?(stylesheet)
extensions = %w(.scss .erb .scss.erb) + [""]
extensions.inject(false) do |truth, extension|
truth || asset_exists?('stylesheets', "#{stylesheet}.css#{extension}")
end
end

Although I know about the assets pipeline and the manifest in application.js, my approach is to keep app's "essential" javascript in application.js and load only the specific javascript for each controller using
<%= javascript_include_tag "application" %>
<%= javascript_include_tag controller_name if File.exists?("#{Rails.root}/app/assets/javascripts/#{controller_name}.js") %>
at the end of my application.html.erb before the </body>
I know that causes the browser to issue one more request to get the controller specific javascript but perhaps after the first request, that javascript will be cached by the browser.

I know this is a pretty old question, but I would suggest using the find_asset function. For Rails 4 you could do something like:
<%= javascript_include_tag params[:controller] if ::Rails.application.assets.find_asset("#{params[:controller]}.js") %>

Custom ViewHelpers to the Rescue!
Add this to your ApplicationHelper:
module ApplicationHelper
def controller_stylesheet(opts = { media: :all })
if Rails.application.assets.find_asset("#{controller_name}.css")
stylesheet_link_tag(controller_name, opts)
end
end
def controller_javascript(opts = {})
if Rails.application.assets.find_asset("#{controller_name}.js")
javascript_include_tag(controller_name, opts)
end
end
end
and you can use them like this in your application.html.erb:
<%= controller_stylesheet %>
<%= controller_javascript %>
Note: This works with all .js, .coffee, .css, .scss even though it just says .css and .js

In Rails3.2 all what you need is to write the list of your js files at the top of application.js file. For example,
//=require jquery.min
//=require jquery_ujs
//=require general
If one of these files don't really exists (btw put them at app/assets/javascripts) - not a problem, no errors will be shown.

Catch the error:
#/app/helpers/application_helpers.rb
def javascript_include_tag_if_assets(*args)
javascript_include_tag(*args)
rescue Sprockets::Rails::Helper::AssetNotFound
''
end
Alternatively, since this asks to check before using the method, javascript_include_tag uses javascript_path so you might as well use it to check and then catch that error. This works for all javascript-like assets. For css-like assets, use stylesheet_path.
#/app/helpers/application_helpers.rb
def javascript_include_tag_if_assets(*files, **opts)
files.each { |file| javascript_path(file) }
javascript_include_tag(*files, **opts)
rescue Sprockets::Rails::Helper::AssetNotFound
''
end

Related

trying to use a news API with rails application

I'm trying to get information from newsapi. I've used the basic structure i found elsewhere for another api to start with but i'm very new to this and not sure what is wrong/how to fix etc. all i'm trying to do is display the titles of the articles on my index page. At the moment, everything displays on the page, like title, author etc as an array but i just want to narrow things down and the syntax for doing so in the view. i've changed my api key to '?' for the time being(i know it should be in the .env file).ive looked at a lot of docs but i cant seem to find an answer. apologies if
this is a broad question.
class TestsController < ApplicationController
require "open-uri"
def index
url = 'https://newsapi.org/v2/top-headlines?sources=techcrunch&apiKey=????????????????????????'
article_serialized = open(url).read
#articles = JSON.parse(article_serialized)
end
end
<% #articles.each do |article| %>
<%= article[1] %>
<% end %>
This should do it
<% #articles["articles"].each do |article| %>
<%= article["title"] %>
<% end %>
Edit : This is what my ruby script looks like that I used for testing
require 'json'
file = File.read 'output.json'
data = JSON.parse(file)
data["articles"].each do |item|
print item["title"]
end

Set local: true as default for form_with in Rails 5

I'm working on a project where we won't be using ajax calls for submitting the forms, so I need to put local: true in every form in the project, as indicated in the rails docs:
:local - By default form submits are remote and unobstrusive XHRs. Disable remote submits with local: true.
Is there any way to set the local option as true by default?
We're using Rails 5 form_with helper like this:
<%= form_with(model: #user, local: true) do |f| %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :email %>
<%= f.email_field :email %>
</div>
<%= f.submit %>
<% end %>
As you've stated it can be set on a per form basis with local: true. However it can be set it globally use the configuration option [form_with_generates_remote_forms][1]. This setting determines whether form_with generates remote forms or not. It defaults to true.
Where to put this configuration? Rails offers four standard spots to place configurations like this. But you probably want this configuration in all enviroments (i.e. development, production, ...). So either set it in an initializer:
# config/initializers/action_view.rb
Rails.application.config.action_view.form_with_generates_remote_forms = false
Or maybe more commonly set in config/application.rb.
# config/application.rb
module App
class Application < Rails::Application
# [...]
config.action_view.form_with_generates_remote_forms = false
end
end
Consider overriding the form_with method:
# form_helper.rb
def form_with(options)
options[:local] = true
super options
end
That should solve it for every form in your application.
The Rails configurations can be set in config/application.rb file.
module App
class Application < Rails::Application
# [...]
config.action_view.form_with_generates_remote_forms = false
end
end
Guy C answer is good, but it's more idiomatic to put all config in this file rather than a separate initializer; That's where most of the Rails dev would expect it.
Note that this would spell disaster if you put it config/development.rb only or other env specific files.

Rails file uploading and Heroku

I have a problem with file uploading on Heroku server.
(Also a hint about the right way of doing such type of things with rails would be greatly appreciated - I'm very new in RoR).
All this code is about uploading some CSV file, then allowing user to tweak couple of settings, and parse file after all. This usually work on localhost (several times I get troubles with value stored in session), but on Heroku it is always die on upload.
In one of the neighborhood questions was written that Heroku store file only during singe instance run, but I still couldn't find anything about this in Heroku's docs. Should I store file data in the db right after upload, so in such case it always be available? The downside - files could be pretty big, about 10-20Mb, it isn't looks nice.
Heroku logs say:
2012-05-21T19:27:20+00:00 app[web.1]: Started POST "/products/upload" for 46.119
.175.140 at 2012-05-21 19:27:20 +0000
2012-05-21T19:27:20+00:00 app[web.1]: Processing by ProductsController#upload
as HTML
2012-05-21T19:27:20+00:00 app[web.1]: Parameters: {"utf8"=>"тЬУ", "authenticit
y_token"=>"aqJFg3aqENfxS2lKCE4o4txxkZTJgPx36SZ7r3nyZBw=", "upload"=>{"my_file"=>
#<ActionDispatch::Http::UploadedFile:0x000000053af020 #original_filename="marina
-AutoPalmaPriceList_2011-07-30.txt", #content_type="text/plain", #headers="Conte
nt-Disposition: form-data; name=\"upload[my_file]\"; filename=\"marina-AutoPalma
PriceList_2011-07-30.txt\"\r\nContent-Type: text/plain\r\n", #tempfile=#<File:/t
mp/RackMultipart20120521-1-10g8xmx>>}, "commit"=>"Upload"}
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]: LoadError (no such file to load -- CSV):
2012-05-21T19:27:20+00:00 app[web.1]: app/controllers/products_controller.rb:8
2:in `upload'
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]:
2012-05-21T19:27:20+00:00 app[web.1]: cache: [POST /products/upload] invalidate,
pass
The code itself:
ProductsController:
def import
respond_to do |format|
format.html
end
end
def import_adjust
case params[:commit]
when "Adjust"
#col_default = params[:col_data]
#abort #col_default.to_yaml
#update csv reader with form data, restore filters from params
when "Complete"
#all ok, read the whole file
#abort params.to_yaml
redirect_to import_complete
else
#col_default = nil
end
#read first part of the file
#tmp = session[:import_file]
#csv = []
source = CSV.open #tmp, {col_sep: ";"}
5.times do
line = source.readline
if line.size>0
#line_size = line.size
#csv.push line
end
end
#generate a selection array
#selection = select_tag 'col_data[]', options_for_select([['name','name'], ['brand','brand'], ['delivery_time','delivery_time'], ['price','price']])
##csv = [selection * line_size] + #csv
end
def import_complete
#remove all items
#todo check products with line items will not be destroyed.
Product.destroy_all
#abort params.to_yaml
map = {}
cnt = 0
#todo check for params count.
params[:col_data].each do |val|
map[cnt] = val if val != 'ignore'
cnt += 1
end
source = CSV.open session[:import_file], {col_sep: ';'}
source.each do |row|
cnt += 1
if row.size > 0
item = Product.new
map.each do |col, attr|
item[attr] = row[col]
end
item[:provider_id] = params[:adjust][:provider]
item.save
#abort item.to_yaml
end
end
#abort map.to_yaml
#todo response needed.
end
def upload
require 'CSV' #looks like I dont need this in fact.
#tmp = params[:upload][:my_file].path #tempfile
#csv = []
#source = CSV.open #tmp, {col_sep: ";"}
session[:import_file] = params[:upload][:my_file].path
respond_to do |format|
format.html { redirect_to action: 'import_adjust' }
end
end
upload.html.erb:
<h1>Uploaded</h1>
<%= #tmp %>
<% #csv.each do |val| %>
<%= val %>
<% end %>
_form_import.html.erb:
<%= form_for :upload, :html => {:multipart => true}, :url => {action: "upload"} do |f| %>
<%= f.file_field :my_file %>
<%= f.submit "Upload" %>
<% end %>
import_adjust.html.erb:
<h1>New product</h1>
<%= form_for :adjust, :url => {action: "import_adjust"} do |f| %>
<% if #csv %>
<table>
<tr>
<% #line_size.times do |cnt| %>
<td>
<%= select_tag 'col_data[]',
options_for_select([
['--ignore--', 'ignore'],
['name','name'],
['brand','brand'],
['delivery_time','delivery_time'],
['price','price']
], #col_default!=nil ? #col_default[cnt] : nil) %>
</td>
<% end %>
</tr>
<% #csv.each do |val| %>
<tr>
<% val.each do |cell| %>
<td>
<%= cell %>
</td>
<% end %>
</tr>
<% end %>
</table>
<% end %>
<%= f.label :delimiter, 'Разделитель' %>
<%= f.text_field :delimiter %>
<br>
<%= f.label :provider, 'Поставщик' %>
<%#todo default empty option needed! Human mistakes warning! %>
<%= f.select :provider, Provider.all.collect { |item| [item.name, item.id] } %>
<br>
<%= f.label :delimiter, 'Разделитель' %>
<%= f.text_field :delimiter %>
<br>
<%# Adjust for proceed adjusting or Complete for parsing %>
<%= f.submit "Adjust" %>
<%= f.submit "Complete" %>
<% end %>
<%= link_to 'Back', products_path %>
Could you paste the entire controller code? The problem is on line #82, but I can't be 100% confident what line that is if you've stripped the class def and before_filters out.
That said, it looks like the problem is with one of the CSV.open lines. The way you're trying to set session[:import_file] is not guaranteed to work. If you ever run the app on more than one dyno you could have the first request served by your web.1 dyno and the second served by web.2, and they have different file systems and would not be able to see the same temp files.
I'd suggest one of the following:
Do all the processing immediately on the upload and avoid the re-direct.
An improvement on that would be to have the upload store the data somewhere shared and accessible (the database or S3) and have start a background job/process to do the processing.
Best of all would be to upload directly to S3 (I believe the S3 Uploader library can do this, there are probably others) and issue a callback to create a background job to process.
That last option means your web dynos are never tied up handling massive uploads and you don't burden the user with waiting for the latency involved in upload to server->store in S3->schedule background job, it is reduced simply to store in S3 from their perspective.
I have an identical scenario as Lifecoder where a user uploads a file, names the columns using a map_fields plugin (by Andrew Timberlake), and then the file is parsed and processed. Here's how I handle it:
file_field = params[options[:file_field]]
map_fields_file_name = "map_fields_#{Time.now.to_i}_#{$$}"
bucket = S3.buckets[CSV_COUPON_BUCKET_NAME] # gets an existing bucket
obj = bucket.objects[map_fields_file_name]
obj.write( file_field.read )
# Save the name and bucket to retrieve on second pass
session[:map_fields][:bucket_name] = map_fields_file_name
Then on the second pass to process the file, I open the file and read it back into temp for the dyno to process:
# Get CSV data out of bucket and stick it back into temp, so we pick up where
# we left off as far as map_fields is concerned.
bucket = S3.buckets[CSV_COUPON_BUCKET_NAME]
obj = bucket.objects[session[:map_fields][:bucket_name]]
temp_path = File.join(Dir::tmpdir, "map_fields_#{Time.now.to_i}_#{$$}")
File.open(temp_path, 'wb') do |f|
f.write obj.read
end
I had to use the plugin so I could modify the code, as obviously the gem is handled by Heroku and doesn't allow for modifications.

Yielding content_for with a block of default content

Our Rails projects make heavy use of content_for. However, we quite often need to render default content if nothing is defined using content_for. For readability and maintainability it makes sense for this default content to be in a block.
We made a helper method in Rails 2.3 and we've now refactored this for Rails 3 (as below).
Both those helpers work very well but I'm wondering if there's a more succinct way I could achieve the same thing in Rails 3.
Rails 2.3:
def yield_or(name, content = nil, &block)
ivar = "#content_for_#{name}"
if instance_variable_defined?(ivar)
content = instance_variable_get(ivar)
else
content = block_given? ? capture(&block) : content
end
block_given? ? concat(content) : content
end
which is useful for doing things like this:
<%= content_for :sidebar_content do %>
<p>Content for the sidebar</p>
<% end %>
<%= yield_or :sidebar_content do %>
<p>Default content to render if content_for(:sidebar_content) isn't specified</p>
<% end %>
Refactored for Rails 3:
def yield_or(name, content = nil, &block)
if content_for?(name)
content_for(name)
else
block_given? ? capture(&block) : content
end
end
This can be done entirely in the view using the content_for? method.
<% if content_for?(:sidebar_content) %>
<%= yield(:sidebar_content) %>
<% else %>
<ul id="sidebar">
<li>Some default content</li>
</ul>
<% end %>

Grab file path from upload

I have an RoR script that when given the file path for an XML file, will parse it. I want to be able to find the file with the GUI, and then allow for my script to run. Here's what I have in the view:
<%= form_for :setup, :html=>{:multipart=>true}, :url=>{action=>"create"} do |f| %>
<%= f.file_filed :my_file %>
<%= f.submit "Upload" %>
<% end %>
The controller for the create area is:
$XML_FILE = params[:my_file]
routers = Router.new
#title = routers.overall_setup
if #title == "Everything worked" then
redirect_to "/"
end
Basically from here, how do you snag the file path out? I know that once the correct file path can be placed to $XML_FILE that the rest of the code works
After you've submited a form you can find upploaded file this way:
path = params[:setup][:my_file][:tempfile].path
where params[:setup][:my_file] is your file_filed name in the form
Rails 3
path = params[:setup][:my_file].path