I have the following code
module EmailHelper
def email_image_tag(image, **options)
attachments.inline[image] = File.read(image_path(image))
image_tag attachments[image].url, **options
end
end
In production this references the correct image with hash, because the image asset is precompiled, however in development this throws a file read exception.
Is there an elegant way to do a File.read without having to check on if Rails.env.development?
The biggest misconception here was that I thought I should point to a digested asset, forgetting that these are meant to be served and not read as a file. Therefore the file can be perfectly loaded from the asset map. I solved the above question as follows:
module MailHelper
def mail_image_tag(image, **options)
path = Rails.root.join('app', 'assets', 'images', image)
attachments.inline[image] = File.read(path)
image_tag attachments[image].url, **options
end
end
Related
I know a simple solution would be just to manually copy over all the files in the Rails Engine's /public folder to the Rails application's /public folder. However, this means that each installation would require manually copying.
Furthermore because the Javascript files that my engine uses have hard-coded image paths, I cannot simply throw all my static files under app/assets or vendor/assets, as then Rails would copy them over to public/assets. I can't change the path where Sprockets outputs the files as I have other gems that expect their assets to be in the default public/assets folder.
I tried doing something like
class Engine < ::Rails::Engine
if Rails.application.config.serve_static_assets
initializer "static assets" do |app|
app.middleware.insert_before(::ActionDispatch::Static, ::ActionDispatch::Static, "#{root}/public")
end
end
end
but this only works for development.
Personally, I believe that the best solution would be to update your javascript files to follow Rails' conventions by replacing the hard-coded images in the javascript with the asset path helper - http://guides.rubyonrails.org/asset_pipeline.html#coding-links-to-assets and then throwing everything into app/assets.
With that said, I can think of situations where you may not want to do that, and that may be the case here.
From your post, I'm guessing that you're precompiling assets for production (i.e. - running rake assets:precompile). In that case, you can just hook into the assets:precompile rake task and copy the files over. It would look something like this in your engine's lib/tasks/my_engine_tasks.rake:
Rake::Task["assets:precompile"].enhance do
Rake::Task["my_engine:copy_assets"].invoke
end
namespace :my_engine do
task :copy_assets => :"assets:environment" do
assets = ["file1", "file2"]
assets.each do |asset|
source_file = File.join(MyEngine::Engine.root, 'public', asset)
dest_file = File.join(Rails.root, 'public', asset)
FileUtils.copy_file source_file, dest_file, true
end
end
end
The above is a simple copy operation but you can also manually run sprockets over those files as well. Take a look at the code for the assets:precompile task (lib/sprockets/rails/task.rb in the sprockets-rails gem) and the Sprockets Manifest class (lib/sprockets/manifest.rb in the sprockets gem).
Seems Rails doesnt provide an obvious way to serve static assets from an engine. So heres my dynamic solution for this problem.
module MyApp
class Engine < ::Rails::Engine
isolate_namespace MyApp
initializer "my_app.assets.precompile" do |app|
app.config.assets.precompile << "my_app_manifest.js" ### manifest file required
### Precompile all static assets
### Note in this example use a non-standard folder (app/assets/public/)
["app/assets/images/", "app/assets/public/"].each do |folder|
dir = app.root.join(folder)
if Dir.exist?(dir)
Dir.glob(File.join(dir, "**/*")).each do |f|
asset_name = f.to_s
.split(folder).last # Remove fullpath
.sub(/^\/*/, '') ### Remove leading '/'
app.config.assets.precompile << asset_name
end
end
end
end
end
end
I am trying to create a temp file in my rails application. Here is the controller code:
private
def tmp_example
temp_file = TempFile.new('logo')
# save uploaded file
File.open(temp_file.path, "w") do |f|
f.write session[:user_params]["logo"].delete(:file).read
f.close
end
end
I have required the tempfile in application.rb
require 'tempfile'
But still i am getting error:
uninitialized constant UsersController::TempFile
Can anyone how to fix this issue thanks.
According to the docs, you should use Tempfile instead of TempFile:
temp_file = Tempfile.new('logo')
I am using CarrierWave for image uploads on a rails-api application which in turn is consumed by a backbone.js client app. I notice that when there is no default image available it returns /assets/default.png. It in turn has to be http://dev-server:3000/assets/default.png. Here are my configuration settings:
# config/environments/development.rb
CarrierWave.configure do |config|
config.asset_host = "http://dev-server:3000"
end
# Image Uploader
....
def default_url
"/assets/default.png"
end
Where am I going wrong?
I'm using Rails 5 API and Carrierwave too.
A lucky guess got it working for me.
I have a file inside app/uploaders/image_uploader.rb.
The asset_host configuration is set inside this file (at least it works for me):
# encoding: utf-8
class ImageUploader < CarrierWave::Uploader::Base
...
def asset_host
return "http://localhost:3000"
end
end
Hope this works for anyone else in the future having this problem.
[Edit (updated answer)]
Updating my answer to setup asset_host in rails config.
Rails.application.configure do
.
.
config.asset_host = 'http://dev-server:3000'
end
Then you can use asset_url method or image_url method of the helper. Since this is an image, I would recommend placing the image in app/assets/images folder and use image_url.
ActionController::Base.helpers.image_url("default.png")
This will give you the following URL:
http://dev-server:3000/images/default.png
You can try it in the console.
[Old Answer]
Looking at Carrierwave Documentation, it seems like your default_url method should look like this (Carrierwave does not automatically perpend asset_host to the default url):
def default_url
ActionController::Base.helpers.asset_path("default.png")
end
I am assuming that asset_host is setup properly in your Rails configuration. If not, please do so.
I would recommend some of your ideas:
I: Put Host in your environment z.B. development.rb
config.asset_host = 'http://localhost:3000'
II: Create file in config/initializers/carrierwave.rb
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
config.storage = :file
config.asset_host = ActionController::Base.asset_host
end
III: Edit your uploader to:
def default_url
"#{asset_host}/images/fallback/" + [version_name, "default.png"].compact.join('_')
end
IV: RESTART your server
I solved the problem by looking into the code of carrierwave. This is what I ended up doing:
def default_url
"#{asset_host}#{ActionController::Base.helpers.asset_path("default.png")}"
end
Also make sure that you include the asset_host configuration in your respective environment files.
I'm using CarrierWave to store files in gridfs, but having problems with opening them from my model.
Here are my configs:
/config/initialize/carrierwave.rb
CarrierWave.configure do |config|
config.grid_fs_database = Mongoid.database.name
config.grid_fs_host = Mongoid.config.master.connection.host
config.storage = :grid_fs
config.grid_fs_access_url = "/files"
end
/app/controllers/gridfs_controller.rb
/require 'mongo'
class GridfsController < ActionController::Metal
def serve
gridfs_path = env["PATH_INFO"].gsub("/files/", "")
begin
gridfs_file = Mongo::GridFileSystem.new(Mongoid.database).open(gridfs_path, 'r')
self.response_body = gridfs_file.read
self.content_type = gridfs_file.content_type
rescue
self.status = :file_not_found
self.content_type = 'text/plain'
self.response_body = ''
end
end
end
/app/uploaders/list_uploader.rb
class ListUploader < CarrierWave::Uploader::Base
storage :grid_fs
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
and in routes
match "/files/uploads/*path" => "gridfs#serve"
So, I have a model, which have a text file
class Campaign
include Mongoid::Document
mount_uploader :list, ListUploader
When I'm calling something like <%=link_to "List", #campaign.list.url %> from my view, it opens fine. But when I'm trying something like File.open("#{campaign.list.url}", "r") from campaign model, it fails. It gives me false even when I'm calling File.exists?("/files/uploads/campaign/list/4eb02c4d6b1c0f02b200000b/list.txt"), which is a proper url for that file. So, the question is how should I call it, to open the file from model? And for some reasons, it is important to open it from model. Any suggestions would help, thank you.
Carrierwave url with mongodb gridfs is not a physical path. Its merely a logical route to download the file from gridfs. Thats why you cannot access it from ruby File.open. Check out the below snippet from rails console trying to open the file from gridfs
File.open(User.first.image.pic.url,'r')
Errno::ENOENT: No such file or directory - /images/uploads/e5a1007d34.jpg
see it throw No such file or directory., So you have to download a file instead opening by
>> require 'open-uri'
>> open('image.jpg', 'wb') do |file|
?> file << open('http://0.0.0.0:3000' + (User.first.image.pic.url)).read
>> p file
>> end
#<File:image.jpg>
=> #<File:image.png (closed)>
What is the best way to get a temporary directory with nothing in it using Ruby on Rails? I need the API to be cross-platform compatible. The stdlib tmpdir won't work.
The Dir object has a method mktmpdir which creates a temporary directory:
require 'tmpdir' # Not needed if you are using rails.
Dir.mktmpdir do |dir|
puts "My new temp dir: #{dir}"
end
The temporary directory will be removed after execution of the block.
The Dir#tmpdir function in the Ruby core (not stdlib that you linked to) should be cross-platform.
To use this function you need to require 'tmpdir'.
A general aprox I'm using now:
def in_tmpdir
path = File.expand_path "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/"
FileUtils.mkdir_p path
yield path
ensure
FileUtils.rm_rf( path ) if File.exists?( path )
end
So in your code you can:
in_tmpdir do |tmpdir|
puts "My tmp dir: #{tmpdir}"
# work with files in the dir
end
The temporary dir will be removed automatically when your method will finish.
Ruby has Dir#mktmpdir, so just use that.
require 'tempfile'
Dir.mktmpdir('prefix_unique_to_your_program') do |dir|
### your work here ###
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tmpdir/rdoc/Dir.html
Or build your own using Tempfile tempfile that is process and thread unique, so just use that to build a quick Tempdir.
require 'tempfile'
Tempfile.open('prefix_unique_to_your_program') do |tmp|
tmp_dir = tmp.path + "_dir"
begin
FileUtils.mkdir_p(tmp_dir)
### your work here ###
ensure
FileUtils.rm_rf(tmp_dir)
end
end
See http://www.ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/Tempfile.html for optional suffix/prefix options.
require 'tmpdir' # not needed if you are loading Rails
tmp_dir = File.join(Dir::tmpdir, "my_app_#{Time.now.to_i}_#{rand(100)}")
Dir.mkdir(tmp_dir)
Works for me.
You can use Dir.mktmpdir.
Using a block will get rid of the temporary directory when it closes.
Dir.mktmpdir do |dir|
File.open("#{dir}/foo", 'w') { |f| f.write('foo') }
end
Or if you need multiple temp directories to exist at the same time, for example
context 'when there are duplicate tasks' do
it 'raises an DuplicateTask error' do
begin
tmp_dir1 = Dir.mktmpdir('foo')
tmp_dir2 = Dir.mktmpdir('bar')
File.new("#{tmp_dir1}/task_name", 'w+')
File.new("#{tmp_dir2}/task_name", 'w+')
expect { subject.filepath('task_name') }.to raise_error(TaskFinder::DuplicateTask)
ensure
FileUtils.remove_entry tmp_dir1
FileUtils.remove_entry tmp_dir2
end
end
end
Dir.mktmpdir creates a temporary directory under Dir.tmpdir (you'll need to require 'tmpdir' to see what that evaluates to).
If you want to use your own path, Dir.mktmpdir takes an optional second argument tmpdir if non-nil value is given. E.g.
Dir.mktmpdir(nil, "/var/tmp") { |dir| "dir is '/var/tmp/d...'" }
I started to tackle this by hijacking Tempfile, see below.
It should clean itself up as Tempfile does, but doesn't always yet..
It's yet to delete files in the tempdir.
Anyway I share this here, might be useful as a starting point.
require 'tempfile'
class Tempdir < Tempfile
require 'tmpdir'
def initialize(basename, tmpdir = Dir::tmpdir)
super
p = self.path
File.delete(p)
Dir.mkdir(p)
end
def unlink # copied from tempfile.rb
# keep this order for thread safeness
begin
Dir.unlink(#tmpname) if File.exist?(#tmpname)
##cleanlist.delete(#tmpname)
#data = #tmpname = nil
ObjectSpace.undefine_finalizer(self)
rescue Errno::EACCES
# may not be able to unlink on Windows; just ignore
end
end
end
This can be used the same way as Tempfile, eg:
Tempdir.new('foo')
All methods on Tempfile , and in turn, File should work.
Just briefly tested it, so no guarantees.
Update: gem install files, then
require "files"
dir = Files do
file "hello.txt", "stuff"
end
See below for more examples.
Here's another solution, inspired by a few other answers. This one is suitable for inclusion in a test (e.g. rspec or spec_helper.rb). It makes a temporary dir based on the name of the including file, stores it in an instance variable so it persists for the duration of the test (but is not shared between tests), and deletes it on exit (or optionally doesn't, if you want to check its contents after the test run).
def temp_dir options = {:remove => true}
#temp_dir ||= begin
require 'tmpdir'
require 'fileutils'
called_from = File.basename caller.first.split(':').first, ".rb"
path = File.join(Dir::tmpdir, "#{called_from}_#{Time.now.to_i}_#{rand(1000)}")
Dir.mkdir(path)
at_exit {FileUtils.rm_rf(path) if File.exists?(path)} if options[:remove]
File.new path
end
end
(You could also use Dir.mktmpdir (which has been around since Ruby 1.8.7) instead of Dir.mkdir but I find the API of that method confusing, not to mention the naming algorithm.)
Usage example (and another useful test method):
def write name, contents = "contents of #{name}"
path = "#{temp_dir}/#{name}"
File.open(path, "w") do |f|
f.write contents
end
File.new path
end
describe "#write" do
before do
#hello = write "hello.txt"
#goodbye = write "goodbye.txt", "farewell"
end
it "uses temp_dir" do
File.dirname(#hello).should == temp_dir
File.dirname(#goodbye).should == temp_dir
end
it "writes a default value" do
File.read(#hello).should == "contents of hello.txt"
end
it "writes a given value" do
# since write returns a File instance, we can call read on it
#goodbye.read.should == "farewell"
end
end
Update: I've used this code to kickstart a gem I'm calling files which intends to make it super-easy to create directories and files for temporary (e.g. unit test) use. See https://github.com/alexch/files and https://rubygems.org/gems/files . For example:
require "files"
files = Files do # creates a temporary directory inside Dir.tmpdir
file "hello.txt" # creates file "hello.txt" containing "contents of hello.txt"
dir "web" do # creates directory "web"
file "snippet.html", # creates file "web/snippet.html"...
"<h1>Fix this!</h1>" # ...containing "<h1>Fix this!</h1>"
dir "img" do # creates directory "web/img"
file File.new("data/hello.png") # containing a copy of hello.png
file "hi.png", File.new("data/hello.png") # and a copy of hello.png named hi.png
end
end
end # returns a string with the path to the directory
Check out the Ruby STemp library: http://ruby-stemp.rubyforge.org/rdoc/
If you do something like this:
dirname = STemp.mkdtemp("#{Dir.tmpdir}/directory-name-template-XXXXXXXX")
dirname will be a string that points to a directory that's guaranteed not to exist previously. You get to define what you want the directory name to start with. The X's get replaced with random characters.
EDIT: someone mentioned this didn't work for them on 1.9, so YMMV.