The problem
I have this search function on my website, it works but for Meetings, and only Meetings, it doesn't. When I try to link to the pdf file that Meeting contains using asset_path I get the 404 page.
This is my Meeting model
class Admin::Meeting < ApplicationRecord
# Image/File Uploader
mount_uploader :agenda, FileUploader
mount_uploader :minutes, FileUploader
has_many :meeting_translations, dependent: :destroy
accepts_nested_attributes_for :meeting_translations
end
My file uploader looks like this, I use CarrierWave
class FileUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
# require 'RMagick'
# include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# before :cache, :setup_available_sizes
# before :cache
# Choose what kind of storage to use for this uploader:
storage :file
permissions 0777
# storage :fog
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
"uploads/#{model.class.name.demodulize.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Provide a default URL as a default if there hasn't been a file uploaded:
# def default_url(*args)
# # For Rails 3.1+ asset pipeline compatibility:
# # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_'))
#
# "/images/fallback/" + [version_name, "default.png"].compact.join('_')
# end
# Process files as they are uploaded:
# process scale: [200, 300]
#
# def scale(width, height)
# # do something
# end
# Create different versions of your uploaded files:
# version :thumb do
# process resize_to_fit: [200, 200]
# end
# Add a white list of extensions which are allowed to be uploaded.
# For images you might use something like this:
def extension_whitelist
%w(pdf doc docx xls xlsx)
end
def size_range
1..10.megabytes
end
end
This is my view where I try to link to agenda (agenda is a file)
<% #meeting_results.each do |result| %>
<% unless result.agenda.nil? %>
<div class="col-sm-12">
<div class="col-sm-1"><i class="far fa-file-pdf fa-2x" aria-hidden="true"></i></div>
<div class="col-sm-10">
<div class="results-link">
<%= result.agenda.file.original_filename %>
</div>
</div>
</div>
<% end %>
<% end %>
This is the error I get
404_error
I usually don't have this problem when I link to PDFs, I don't know if it's because I also have another file called minutes for Meeting? Anyways, any help is appreciated, thanks.
Edit
I also noticed that the path returned from asset_path does not contain the Meetings ID. This is the path it gives meuploads/meeting/agenda/Bathurst_Regular_Public_Meeting_Agenda_May_15-2017.pdf when the file is really located in uploads/meeting/agenda/ID/Bathurst_Regular_Public_Meeting_Agenda_May_15-2017.pdf
I feel like a complete idiot, my query for finding the Meeting's that matched my search term was wrong.
What I had
#meeting_results = Admin::Meeting.select(:agenda).where("meetings.agenda LIKE ? OR meetings.minutes LIKE ?", "%#{query}%", "%#{query}%").distinct
What I replaced it with
#meeting_results = Admin::Meeting.where("meetings.agenda LIKE ? OR meetings.minutes LIKE ?", "%#{query}%", "%#{query}%").distinct
I had the wrong query due to a change I had made and decided not to go with it. Hope this maybe helps someone some day :(
Related
I've written a simple plugin that generates a small JSON file
module Jekyll
require 'pathname'
require 'json'
class SearchFileGenerator < Generator
safe true
def generate(site)
output = [{"title" => "Test"}]
path = Pathname.new(site.dest) + "search.json"
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write("---\nlayout: null\n---\n")
f.write(output.to_json)
end
# 1/0
end
end
end
But the generated JSON file gets deleted every time Jekyll runs to completion. If I uncomment the division by zero line and cause it to error out, I can see that the search.json file is being generated, but it's getting subsequently deleted. How do I prevent this?
I found the following issue, which suggested adding the file to keep_files: https://github.com/jekyll/jekyll/issues/5162 which worked:
The new code seems to avoid search.json from getting deleted:
module Jekyll
require 'pathname'
require 'json'
class SearchFileGenerator < Generator
safe true
def generate(site)
output = [{"title" => "Test"}]
path = Pathname.new(site.dest) + "search.json"
FileUtils.mkdir_p(File.dirname(path))
File.open(path, 'w') do |f|
f.write("---\nlayout: null\n---\n")
f.write(output.to_json)
end
site.keep_files << "search.json"
end
end
end
Add your new page to site.pages :
module Jekyll
class SearchFileGenerator < Generator
def generate(site)
#site = site
search = PageWithoutAFile.new(#site, site.source, "/", "search.json")
search.data["layout"] = nil
search.content = [{"title" => "Test 32"}].to_json
#site.pages << search
end
end
end
Inspired by jekyll-feed code.
I am new to RoR and I am following the Agile Web Development with Rails 4 book and I am to the point where we add CSS styling to the store front page (page 135 "8.1 Iteration C1: Creating the Catalog Listing").
However, I did exactly as the book stated but the CSS style is not being loaded when I load the page.
I did some research and from what I understand, everything is correct.
Does anyone see anything wrong or what I need to add?
I have tried everything here but nothing worked: Agile web development with rails” book: CSS not applied
UPDATE Looks like the application is using /assets/scaffold.css.scss for styling. If I go to this file a change colors, they reflect on the StoreControllers view!!?? What is wrong here??!!
I cannot figure out why the application is using the incorrect stylesheet...
BTW I also ran RAILS_ENV=production bundle exec rake assets:precompile and made sure the assets were included in public/assets/ folder
Here is my code:
Depot/app/assets/stylesheets/store.css.scss
.store {
h1 {
margin:0;
padding-bottom: 0.5em;
font: 150% san-serif;
color: #226;
border-bottom: 3px dotted #77d;
}
.entry {
overflow: auto;
margin-top: 1em;
border-bottom: 1px dotted #77d;
min-hieght:100px;
img {
width: 80px;
margin-right: 5px;
margin-bottom: 5px;
position: absolute;
}
h3 {
font-size: 120%;
font-family: sans-serif;
margin-left: 100px;
margin-top: 0;
margin-bottom: 2px;
color: #227;
}
p,div.price_line{
margin-left: 100px;
margin-top: 0.5em;
margin-bottom: 0.8em;
}
.price{
color: #44a;
font-weight: bold;
margin-right: 3em;
}
}
}
// Place all the styles related to the Store controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: http://sass-lang.com/
Depot/app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>Application</title>
<%= stylesheet_link_tag "application", media: "all", "data-turbolinks-track" => true %>
<%= javascript_include_tag "application", "data-turbolinks-track" => true %>
<%= csrf_meta_tags %>
</head>
<body class='<%= controller.controller_name%>'>
<%= yield %>
</body>
</html>
Depot/app/controllers/store/store_controller.rb
class StoreController < ApplicationController
def index
#products = Product.order(:title)
end
end
Depot/app/views/store/index.html.erb
<% if notice %>
<p id= "notice" ><%= notice %></p>
<% end %>
<h1>Your Pragmatic Catalog</h1>
<% #products.each do |product| %>
<div class= ".entry" >
<%= image_tag(product.image_url)%>
<h3><%= product.title %></h3>
<%= sanitize(product.description)%>
<div class= "price_line" >
<span class= "price" ><%= product.price %></span>
</div>
</div>
<% end %>
Depot/app/assets/stylesheets/application.css
/*
* This is a manifest file that'll be compiled into application.css, which will include all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
*
* You're free to add application-wide styles to this file and they'll appear at the top of the
* compiled file, but it's generally better to create a new file per style scope.
*
*= require_self
*= require_tree .
*/
Depot/config/environments/production.rb
Depot::Application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both thread web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = true
config.assets.precompile = ['*.js', '*.css', '*.css.erb']
# Generate digests for assets URLs.
config.assets.digest = true
# Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Set to :debug to see everything in the log.
config.log_level = :info
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# config.assets.precompile += %w( search.js )
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
end
Depot::Application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both thread web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
config.eager_load = true
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable Rails's static asset server (Apache or nginx will already do this).
config.serve_static_assets = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
# config.assets.css_compressor = :sass
# Do not fallback to assets pipeline if a precompiled asset is missed.
config.assets.compile = true
config.assets.precompile = ['*.js', '*.css', '*.css.erb']
# Generate digests for assets URLs.
config.assets.digest = true
# Version of your assets, change this if you want to expire all your assets.
config.assets.version = '1.0'
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Set to :debug to see everything in the log.
config.log_level = :info
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# config.assets.precompile += %w( search.js )
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
end
This solved it for me.
CSS is not loading in app
Be sure to test your CSS with something more obvious, like
color: red;
somewhere. The agile book is only aligning the text here. It could be easily missed.
I have the code that implement csv upload like this:
def Hotel.import(file)
CSV.foreach(file.path, headers: true) do |row|
product = find_by_id(row["id"]) || new
product.attributes = row.to_hash
product.save
end
end
def import
Hotel.import(params[:file])
redirect_to root_url, notice: "Product was successfully Imported."
end
so how do I write rspec test for this?
There are lots of ways to write controller specs. There are many good resources online outlining how to write them in different styles. I suggest starting with the RSpec docs on controller specs:
https://github.com/rspec/rspec-rails#controller-specs
https://www.relishapp.com/rspec/rspec-rails/v/2-14/docs/controller-specs
In general they go something like:
require "spec_helper"
describe ProductsController do
describe "POST #import" do
it "redirects to the home page" do
allow(Hotel).to receive(:import).with("foo.txt")
post :import, file: "foo.txt"
expect(response).to redirect_to root_url
end
it "adds a flash notice" do
allow(Hotel).to receive(:import).with("foo.txt")
post :import, file: "foo.txt"
expect(flash[:notice]).to eq "Product was successfully imported."
end
it "imports the hotel file" do
expect(Hotel).to receive(:import).with("foo.txt")
post :import, file: "foo.txt"
end
end
end
If any one needed model tests for rspec.
require 'rails_helper'
RSpec.describe Product, type: :model do
describe 'import' do
before :each do
#file = fixture_file_upload('data.csv', 'csv')
end
context 'when file is provided' do
it 'imports products' do
Product.import(#file)
expect(Product.find_by(part_number: '0121G00047P').description)
.to eq 'GALV x FAB x .026 x 29.88 x 17.56'
end
end
end
end
I have my Report model:
class Report < ActiveRecord::Base
belongs_to :user
attr_accessible :ready_status, :document
mount_uploader :document, DocumentUploader
def attach( report_file )
self.update_attributes( :document => File.open( report_file ), :ready_status => true )
end
end
This model has attach metod, which i use to save document and other param. Now i want to test that this function works.
/spec/models/report_spec.rb
# encoding: utf-8
require 'spec_helper'
describe Report do
before(:each) do
#user = User.make!
end
...
context "File's saving" do
before(:each) do
#report = #user.reports.create
#csv_report_file = "#{Rails.root}/spec/files/report.csv"
end
it "CSV should be saved" do
csv_report_filename = #csv_report_file.split("/").last
#report.attach #csv_report_file
#report.reload
#report.document.file.filename.should == csv_report_filename
end
end
end
When i try to saving file from /spec/files i get such error:
Report File's saving CSV should be saved
Failure/Error: #report.document.file.filename.should == csv_report_filename
NoMethodError:
undefined method `filename' for nil:NilClass
But when i try another file from another folder (for example "#{Rails.root}/samples/my-report.csv") then my test passes.
How can i fix that?
Oh, i found the answer. Carrierwave doesn't save empty file and i had one. When i added some data in the file (/spec/files/report.csv) my problem has gone.
i want to upload CSV files through the activeadmin panel.
on the index page from the resource "product" i want a button next to the "new product" button with "import csv file".
i dont know where to start.
in the documentation is something about collection_action, but with the code below i have no link at the top.
ActiveAdmin.register Post do
collection_action :import_csv, :method => :post do
# Do some CSV importing work here...
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
anyone here who use activeadmin and can import csv data?
Continuing from Thomas Watsons great start to the answer which helped me get my bearings before figuring the rest of it out.
The code blow allows not just CSV upload for the example Posts model but for any subsequent models thereafter. all you need to do is copy the action_item ands both collection_actions from the example into any other ActiveAdmin.register block and the functionality will be the same. hope this helps.
app/admin/posts.rb
ActiveAdmin.register Post do
action_item :only => :index do
link_to 'Upload CSV', :action => 'upload_csv'
end
collection_action :upload_csv do
render "admin/csv/upload_csv"
end
collection_action :import_csv, :method => :post do
CsvDb.convert_save("post", params[:dump][:file])
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
app/models/csv_db.rb
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
CSV.parse(csv_file) do |row|
target_model = model_name.classify.constantize
new_object = target_model.new
column_iterator = -1
target_model.column_names.each do |key|
column_iterator += 1
unless key == "ID"
value = row[column_iterator]
new_object.send "#{key}=", value
end
end
new_object.save
end
end
end
end
note: this example does a check to see whether or not the first column is an ID column, it then skips that column as rails will assign an ID to the new object (see example CSV below for reference)
app/views/admin/csv/upload_csv.html.haml
= form_for :dump, :url=>{:action=>"import_csv"}, :html => { :multipart => true } do |f|
%table
%tr
%td
%label{:for => "dump_file"}
Select a CSV File :
%td
= f.file_field :file
%tr
%td
= submit_tag 'Submit'
app/public/example.csv
"1","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"2","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"3","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"4","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"5","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
note: quotations not always needed
Adding a collection_action does not automatically add a button linking to that action. To add a button at the top of the index screen you need to add the following code to your ActiveAdmin.register block:
action_item :only => :index do
link_to 'Upload CSV', :action => 'upload_csv'
end
But before calling the collection action you posted in your question, you first need the user to specify which file to upload. I would personally do this on another screen (i.e. creating two collection actions - one being a :get action, the other being your :post action). So the complete AA controller would look something like this:
ActiveAdmin.register Post do
action_item :only => :index do
link_to 'Upload posts', :action => 'upload_csv'
end
collection_action :upload_csv do
# The method defaults to :get
# By default Active Admin will look for a view file with the same
# name as the action, so you need to create your view at
# app/views/admin/posts/upload_csv.html.haml (or .erb if that's your weapon)
end
collection_action :import_csv, :method => :post do
# Do some CSV importing work here...
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
#krhorst, I was trying to use your code, but unfortunately it sucks on big imports. It eat so much memory =( So I decided to use own solution based on activerecord-import gem
Here it is https://github.com/Fivell/active_admin_import
Features
Encoding handling
Support importing with ZIP file
Two step importing (see example2)
CSV options
Ability to prepend CSV headers automatically
Bulk import (activerecord-import)
Ability to customize template
Callbacks support
Support import from zip file
....
Based on ben.m's excellent answer above I replaced the csv_db.rb section suggested with this:
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
begin
target_model = model_name.classify.constantize
CSV.foreach(csv_data.path, :headers => true) do |row|
target_model.create(row.to_hash)
end
rescue Exception => e
Rails.logger.error e.message
Rails.logger.error e.backtrace.join("\n")
end
end
end
end
While not a complete answer I did not want my changes to pollute ben.m's answer in case I did something egregiously wrong.
expanding on ben.m's response which I found very useful.
I had issues with the CSV import logic (attributes not lining up and column iterator not functioning as required) and implemented a change which instead utilizes a per line loop and the model.create method. This allows you to import a .csv with the header line matching the attributes.
app/models/csv_db.rb
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
lines = CSV.parse(csv_file)
header = lines.shift
lines.each do |line|
attributes = Hash[header.zip line]
target_model = model_name.classify.constantize
target_model.create(attributes)
end
end
end
end
So your imported CSV file can look like this (use to match up with model attributes):
importExample.csv
first_name,last_name,attribute1,attribute2
john,citizen,value1,value2
For large excel which takes time on normal process, I created a gem that process Excel sheets using an active job and display results using action cable(websockets)
https://github.com/shivgarg5676/active_admin_excel_upload
Some of the solutions above worked pretty well. I ran into challenges in practice that I solved here below. The solved problems are:
Importing CSV data with columns in different orders
Preventing errors caused by hidden characters in Excel CSVs
Resetting the database primary_key so that the application can continue to add records after the import
Note: I took out the ID filter so I could change IDs for what I'm working on, but most use cases probably want to keep it in.
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
csv_file.to_s.force_encoding("UTF-8")
csv_file.sub!("\xEF\xBB\xBF", '')
target_model = model_name.classify.constantize
headers = csv_file.split("\n")[0].split(",")
CSV.parse(csv_file, headers: true) do |row|
new_object = target_model.new
column_iterator = -1
headers.each do |key|
column_iterator += 1
value = row[column_iterator]
new_object.send "#{key.chomp}=", value
end
new_object.save
end
ActiveRecord::Base.connection.reset_pk_sequence!(model_name.pluralize)
end
end
end