Background Video Processing with Rails - ruby-on-rails-3

I am trying to get uploaded videos to be converted in the background, running windows. Some of what I am using:
gem 'paperclip'
gem 'delayed_job_active_record'
gem 'ffmpeg'
I have edited the registry to allow the ffmpeg command to be ran from anywhere, I get a popup that I assume is ffmpeg because it goes away too quickly, guess the command is wrong so if anyone knows what's wrong with it please let me know. But the real problem is that it just hangs there, it says:
[2012-12-09 22:47:03] ERROR invalid body size.
[2012-12-09 22:47:03] ERROR Errno::ECONNABORTED: An established connection was a
borted by the software in your host machine.
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i
n `write'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i
n `<<'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:396:i
n `_write_data'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:368:i
n `send_body_string'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:249:i
n `send_body'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpresponse.rb:152:i
n `send_response'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/httpserver.rb:110:in
`run'
C:/RailsInstaller/Ruby1.9.3/lib/ruby/1.9.1/webrick/server.rb:191:in `blo
ck in start_thread'
Does anyone know how to properly get this working? I've went through a few tutorials that have bits and pieces of what I need but I can't get them working together. Here's what I have so far, lemme know if you need more:
Model:
class Video < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
attr_accessible :video, :user_id, :video_file_name, :title, :public, :description, :views
has_attached_file :video, url: "/users/:user_id/videos/:id/:basename_:style.:extension"
#process_in_background :video #causes death
validates :video, presence: true
validates :description, presence: true, length: { minimum: 5, maximum: 100}
validates :title, presence: true, length: { minimum: 1, maximum: 15 }
validates_attachment_size :video, less_than: 1.gigabytes
validates_attachment :video, presence: true
default_scope order: 'created_at DESC'
Paperclip.interpolates :user_id do |attachment, style|attachment.instance.user_id
end
#before_post_process do |video|
# false if video.status == "converting"
#end
def perform
command = <<-end_command
start ffmpeg -i #{ '/public/users/:user_id/videos/:id/:basename_:style.:extension' } -ar 22050 -ab 32 -s 1280x720 -vcodec webm -r 25 -qscale 8 -f webm -y #{ '/public/users/:user_id/videos/:id/:basename_.webm' }
end_command
success = system(command)
logger.debug 'Converting File: ' + success.to_s
if success && $?.exitstatus.to_i == 0
#self.converted!
self.status = "converted"
else
#self.failure!
self.status = "failed"
end
end
handle_asynchronously :perform
def self.search(search)
if search
find(:all, conditions: ["public = 't' AND title LIKE ?", "%#{search}%"], order: "created_at DESC")
else
find(:all, conditions: ["public = 't'"], order: "created_at DESC")
end
end
def self.admin_search(search)
if search
find(:all, conditions: ['title LIKE ?', "%#{search}%"], order: "created_at DESC")
else
find(:all, order: "created_at DESC")
end
end
private
# This updates the stored filename with the new flash video file
def set_new_filename
#update_attribute(:filename, "#{filename}.#{id}.webm")
update_attribute(:content_type, "video/x-webm")
end
end
Controller:
class VideosController < ApplicationController
before_filter :signed_in_user, only: [:upload, :update, :destroy]
before_filter :admin_user, only: :admin_index
def upload
#video = Video.new
# generate a unique id for the upload
#uuid = (0..29).to_a.map {|x| rand(10)}
end
def create
#video = Video.new(params[:video])
#video.user_id = current_user.id
if #video.save
#video.delay.perform
flash[:success] = "Uploaded Succefully!"
redirect_to #video.user
Delayed::Worker.new.start
else
render 'upload'
end
end
def show
#video = Video.find(params[:id])
#comments = #video.comments.paginate(page: params[:page], per_page: 6)
if !#video.public
if !signed_in? || current_user.id != #video.user_id && !current_user.admin && !current_user.approved?(#video.user)
flash[:notice] = "Video is private"
redirect_to root_path
end
end
end
def update
#video = Video.find(params[:id])
if #video.update_attributes(params[:video])
flash[:success] = "Video preferences saved"
else
flash[:fail] = "Failed to update video preferences"
end
redirect_to :back
end
def destroy
#video = Video.find(params[:id])
#video.destroy
flash[:deleted] = "Deleted Succefully!"
redirect_to :back
end
def index
#videos = Video.paginate(page: params[:page], per_page: 6).search(params[:search])
end
def admin_index
#videos = Video.paginate(page: params[:page], per_page: 6).admin_search(params[:search])
end
def ajax_video_comments
#video = Video.find(params[:id])
#comments = #video.comments.paginate(page: params[:page], per_page: 6)
respond_to do |format|
format.js { render partial: 'shared/comments', content_type: 'text/html' }
end
end
def ajax_video_watched
#video = Video.find(params[:id])
#video.views += 1
#video.save
end
private
def signed_in_user
redirect_to root_path, notice: "Please Login." unless signed_in?
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end

Your table should have these columns:
video_file_name
video_content_type
video_file_size
video_updated_at
video_meta
I added some additional paperclip magic to you model, obviously you can tweak the settings for ffmpeg. This is not all original code but I can't remember where I found bits and pieces so if someone recognizes it feel free to take credit.
class Video < ActiveRecord::Base
belongs_to :user
has_many :comments, dependent: :destroy
attr_accessible :video, :user_id, :video_file_name,
:title, :public, :description, :views
has_attached_file :video,
url: "/users/:user_id/videos/:id/:basename_:style.:extension"
styles: {
:original => { :geometry => "1280x720", :format => 'mp4', :streaming => true, :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '1250k', maxrate: '1250k', bufsize: '2500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} },
:medium => { :geometry => "854x480", :format => 'mp4', :streaming => true, :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '750k', maxrate: '750k', bufsize: '1500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} },
:small => { :geometry => '640x360', :format => 'mp4', :streaming => true, :convert_options => { :input => {}, :output => {'c:v' => 'libx264', vprofile: 'high', preset: 'medium', 'b:v' => '250k', maxrate: '250k', bufsize: '500k', pix_fmt: 'yuv420p', flags: '+mv4+aic', threads: 'auto', 'b:a' => '128k', strict: '-2'}} },
:thumb => { :geometry => "160x90", :format => 'jpg', :time => 10 }
},
processors: [:ffmpeg, :qtfaststart]
validates :video, presence: true
validates :description, presence: true, length: { minimum: 5, maximum: 100}
validates :title, presence: true, length: { minimum: 1, maximum: 15 }
validates_attachment_size :video, less_than: 1.gigabytes
validates_attachment :video, presence: true
default_scope order: 'created_at DESC'
# cancel post-processing now, and set flag...
before_post_process do |video|
if video.status == nil
video.status = "queuing"
false # halts processing
end
end
# ...and perform after save in background
after_commit do |video|
if video.status == "queuing"
Delayed::Job.enqueue VideoJob.new(video.id), :queue => 'video'
video.status == "queued"
video.save(validations: false)
end
end
# generate styles (downloads original first)
def regenerate_styles!
self.video.reprocess!
end
# detect if our source file has changed
def video_changed?
self.video_file_size_changed? ||
self.video_file_name_changed? ||
self.video_content_type_changed? ||
self.video_updated_at_changed?
end
# Class to perform with delayed jobs
class VideoJob < Struct.new(:video_id)
def perform
video = Video.find(self.video_id)
video.status = "processing"
video.save(validations: false)
video.regenerate_styles!
end
def success(job)
video = Video.find(self.video_id)
video.status = "complete"
video.save(:validate => false)
end
def error(job, exception)
video = Video.find(self.video_id)
video.status = "error"
video.save(:validate => false)
end
end
end
Paperclip Processors (/lib/paperclip_processors/ffmpeg.rb):
module Paperclip
class Ffmpeg < Processor
attr_accessor :geometry, :format, :whiny, :convert_options
# Creates a Video object set to work on the +file+ given. It
# will attempt to transcode the video into one defined by +target_geometry+
# which is a "WxH"-style string. +format+ should be specified.
# Video transcoding will raise no errors unless
# +whiny+ is true (which it is, by default. If +convert_options+ is
# set, the options will be appended to the convert command upon video transcoding.
def initialize file, options = {}, attachment = nil
#convert_options = {
:input => {},
:output => { :y => nil }
}
unless options[:convert_options].nil? || options[:convert_options].class != Hash
unless options[:convert_options][:input].nil? || options[:convert_options][:input].class != Hash
#convert_options[:input].reverse_merge! options[:convert_options][:input]
end
unless options[:convert_options][:output].nil? || options[:convert_options][:output].class != Hash
#convert_options[:output].reverse_merge! options[:convert_options][:output]
end
end
#geometry = options[:geometry]
#file = file
#keep_aspect = !#geometry.nil? && #geometry[-1,1] != '!'
#pad_only = #keep_aspect && #geometry[-1,1] == '#'
#enlarge_only = #keep_aspect && #geometry[-1,1] == '<'
#shrink_only = #keep_aspect && #geometry[-1,1] == '>'
#whiny = options[:whiny].nil? ? true : options[:whiny]
#format = options[:format]
#time = options[:time].nil? ? 3 : options[:time]
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
#meta = identify
#pad_color = options[:pad_color].nil? ? "black" : options[:pad_color]
attachment.instance_write(:meta, #meta)
end
# Performs the transcoding of the +file+ into a thumbnail/video. Returns the Tempfile
# that contains the new image/video.
def make
src = #file
dst = Tempfile.new([#basename, #format ? ".#{#format}" : ''])
dst.binmode
parameters = []
# Add geometry
if #geometry
# Extract target dimensions
if #geometry =~ /(\d*)x(\d*)/
target_width = $1
target_height = $2
end
# Only calculate target dimensions if we have current dimensions
unless #meta[:size].nil?
current_geometry = #meta[:size].split('x')
# Current width and height
current_width = current_geometry[0]
current_height = current_geometry[1]
if #keep_aspect
if #enlarge_only
if current_width.to_i < target_width.to_i
# Keep aspect ratio
width = target_width.to_i
height = (width.to_f / (#meta[:aspect].to_f)).to_i
#convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}"
else
return nil
end
elsif #shrink_only
if current_width.to_i > target_width.to_i
# Keep aspect ratio
width = target_width.to_i
height = (width.to_f / (#meta[:aspect].to_f)).to_i
#convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}"
else
return nil
end
elsif #pad_only
# Keep aspect ratio
width = target_width.to_i
height = (width.to_f / (#meta[:aspect].to_f)).to_i
# We should add half the delta as a padding offset Y
pad_y = (target_height.to_f - height.to_f) / 2
if pad_y > 0
#convert_options[:output][:vf] = "scale=#{width}:-1,pad=#{width.to_i}:#{target_height.to_i}:0:#{pad_y}:##pad_color"
else
#convert_options[:output][:vf] = "scale=#{width}:-1,crop=#{width.to_i}:#{height.to_i}"
end
else
# Keep aspect ratio
width = target_width.to_i
height = (width.to_f / (#meta[:aspect].to_f)).to_i
#convert_options[:output][:s] = "#{width.to_i/2*2}x#{height.to_i/2*2}"
end
else
# Do not keep aspect ratio
#convert_options[:output][:s] = "#{target_width.to_i/2*2}x#{target_height.to_i/2*2}"
end
end
end
# Add format
case #format
when 'jpg', 'jpeg', 'png', 'gif' # Images
#convert_options[:input][:ss] = #time
#convert_options[:output][:vframes] = 1
#convert_options[:output][:f] = 'image2'
end
# Add source
parameters << #convert_options[:input].map { |k,v| "-#{k.to_s} #{v} "}
parameters << "-i ':source'"
parameters << #convert_options[:output].map { |k,v| "-#{k.to_s} #{v} "}
parameters << "':dest'"
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
begin
success = Paperclip.run("ffmpeg", parameters, :source => "#{File.expand_path(src.path)}", :dest => File.expand_path(dst.path))
rescue Cocaine::ExitStatusError => e
raise Paperclip::Error, "error while processing video for #{#basename}: #{e}" if #whiny
end
dst
end
def identify
meta = {}
command = "ffmpeg -i \"#{File.expand_path(#file.path)}\" 2>&1"
ffmpeg = IO.popen(command)
ffmpeg.each("\r") do |line|
# Matching lines like:
# Video: h264, yuvj420p, 640x480 [PAR 72:72 DAR 4:3], 10301 kb/s, 30 fps, 30 tbr, 600 tbn, 600 tbc
if line.include?(' Video: ')
start = line.index('Video:')
items = line[start, 150].split(',')
size = items[2].strip!.split(' ').first
meta[:size] = size.to_s
meta[:aspect] = size.split('x').first.to_f / size.split('x').last.to_f
end
# Matching Duration: 00:01:31.66, start: 0.000000, bitrate: 10404 kb/s
if line =~ /Duration:(\s.?(\d*):(\d*):(\d*\.\d*))/
meta[:length] = $2.to_s + ":" + $3.to_s + ":" + $4.to_s
end
end
meta
end
end
class Attachment
def meta
instance_read(:meta)
end
end
end
Paperclip Processors (/lib/paperclip_processors/qtfaststart.rb):
module Paperclip
class Qtfaststart < Processor
attr_accessor :streaming, :format, :whiny
# Creates a Video object set to work on the +file+ given. It
# will attempt to reposition the moov atom in the video given
# if +streaming+ is set.
def initialize file, options = {}, attachment = nil
#streaming = options[:streaming]
#file = file
#whiny = options[:whiny].nil? ? true : options[:whiny]
#format = options[:format]
#current_format = File.extname(#file.path)
#basename = File.basename(#file.path, #current_format)
#meta = attachment.meta
attachment.instance_write(:meta, #meta)
end
# Performs the atom repositioning on +file+.
# Returns the Tempfile that contains the new video or the original
# file if +streaming+ wasn't set.
def make
return #file unless #streaming
src = #file
dst = Tempfile.new([#basename, #format ? ".#{#format}" : ''])
dst.binmode
parameters = []
# Add source
parameters << ":source"
# Add destination
parameters << ":dest"
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
Paperclip.log("[qtfaststart] #{parameters}")
begin
success = Paperclip.run("qt-faststart", parameters, :source => "#{File.expand_path(src.path)}", :dest => File.expand_path(dst.path))
rescue Cocaine::ExitStatusError => e
raise PaperclipError, "error while processing video for #{#basename}: #{e}" if #whiny
end
dst
end
end
class Attachment
def meta
instance_read(:meta)
end
end
end
You controller could use some more cleaning but I just made adjustments to make it work with my other changes.
class VideosController < ApplicationController
before_filter :signed_in_user, only: [:upload, :update, :destroy]
before_filter :admin_user, only: :admin_index
def upload
#video = Video.new
# generate a unique id for the upload
#uuid = (0..29).to_a.map {|x| rand(10)}
end
def create
#video = Video.new(params[:video])
#video.user_id = current_user.id
if #video.save
flash[:success] = "Uploaded Succefully!"
redirect_to #video.user
else
render 'upload'
end
end
def show
#video = Video.find(params[:id])
#comments = #video.comments.paginate(page: params[:page], per_page: 6)
if !#video.public
if !signed_in? || current_user.id != #video.user_id && !current_user.admin && !current_user.approved?(#video.user)
flash[:notice] = "Video is private"
redirect_to root_path
end
end
end
def update
#video = Video.find(params[:id])
if #video.update_attributes(params[:video])
flash[:success] = "Video preferences saved"
else
flash[:fail] = "Failed to update video preferences"
end
redirect_to :back
end
def destroy
#video = Video.find(params[:id])
#video.destroy
flash[:deleted] = "Deleted Succefully!"
redirect_to :back
end
def index
#videos = Video.paginate(page: params[:page], per_page: 6).search(params[:search])
end
def admin_index
#videos = Video.paginate(page: params[:page], per_page: 6).admin_search(params[:search])
end
def ajax_video_comments
#video = Video.find(params[:id])
#comments = #video.comments.paginate(page: params[:page], per_page: 6)
respond_to do |format|
format.js { render partial: 'shared/comments', content_type: 'text/html' }
end
end
def ajax_video_watched
#video = Video.find(params[:id])
#video.views += 1
#video.save
end
private
def signed_in_user
redirect_to root_path, notice: "Please Login." unless signed_in?
end
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
You can run a delayed_jobs worker thread how ever works best for you. I may have made some mistakes in here but I tried to adapt my method to your current model.

Bit late, but we use the paperclip-ffmeg gem - something you might want to check out
All you need to do is put them gem into your gemfile, and then you just have to define the processor as :ffmpeg.
Here's a live example from our code:
class Attachment < ActiveRecord::Base
has_attached_file :attachment,
styles: lambda { |a| a.instance.is_image? ? {:small => "x200>", :medium => "x300>", :large => "x400>"} : {:thumb => { :geometry => "100x100#", :format => 'jpg', :time => 10}, :medium => { :geometry => "300x300#", :format => 'jpg', :time => 10}}},
:processors => lambda { |a| a.is_video? ? [ :ffmpeg ] : [ :thumbnail ] }
def is_video?
attachment.instance.attachment_content_type =~ %r(video)
end
def is_image?
attachment.instance.attachment_content_type =~ %r(image)
end
end

Related

nav tabs used by two models and controllers with has_many and blongs_to

I have two models with has_many and belongs_to with nested resources and independent views. I have created nav-tabs which I wish to be rendered by both controllers. The nav-tabs works for trip view but does not work in location index. I need to pass local.. something to get working and I don't know how. This is the error I get.
No route matches {:action=>"show", :controller=>"trips", :trip_id=>"13"} missing required keys: [:id] for this line:
a.slow href=send("trip_url") Trip
Trip Model
class Trip < ApplicationRecord
has_many :user_trips
has_many :users, through: :user_trips, dependent: :destroy
belongs_to :creator, class_name: "User"
# has_many :locations, dependent: :destroy
# belongs_to :location, optional: true, inverse_of: :locations
has_many :locations
accepts_nested_attributes_for :locations, :reject_if => :all_blank, :allow_destroy => true
end
location model
class Location < ApplicationRecord
# has_many :trips, inverse_of: :trips
belongs_to :trip, optional: true
# accepts_nested_attributes_for :trips, :reject_if => :all_blank, :allow_destroy => true
end
Routes
Rails.application.routes.draw do
resources :locations
resources :trips do
resources :locations
end
end
trip controller
class TripsController < ApplicationController
before_action :set_trip, only: [:show, :edit, :update, :destroy]
# GET /trips
# GET /trips.json
def index
#trips = Trip.all
end
# GET /trips/1
# GET /trips/1.json
def show
end
# GET /trips/new
def new
#user = current_user
#trip = current_user.trips.build
end
# GET /trips/1/edit
def edit
#user = current_user
end
# POST /trips
# POST /trips.json
def create
#user = current_user
#trip = current_user.trips.build(trip_params)
respond_to do |format|
if #trip.save
# #trip.users << #user
format.html { redirect_to #trip, notice: 'Trip was successfully created.' }
format.json { render :show, status: :created, location: #trip }
else
format.html { render :new }
format.json { render json: #trip.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /trips/1
# PATCH/PUT /trips/1.json
def update
respond_to do |format|
if #trip.update(trip_params)
format.html { redirect_to #trip, notice: 'Trip was successfully updated.' }
format.json { render :show, status: :ok, location: #trip }
else
format.html { render :edit }
format.json { render json: #trip.errors, status: :unprocessable_entity }
end
end
end
# DELETE /trips/1
# DELETE /trips/1.json
def destroy
#trip.destroy
respond_to do |format|
format.html { redirect_to trips_url, notice: 'Trip was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_trip
#trip = Trip.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def trip_params
params.require(:trip).permit(:name, :description, :creator_id, :location_id, user_ids: [])
end
end
location controller
class LocationsController < ApplicationController
# before_action :set_location, only: [:show, :edit, :update, :destroy]
# before_action :set_trip
# GET /locations
# GET /locations.json
def index
trip = Trip.find(params[:trip_id])
#locations = trip.locations
# #locations = trip.locations
end
# GET /locations/1
# GET /locations/1.json
def show
#trip = Trip.find(params[:trip_id])
trip = Trip.find(params[:trip_id])
#location = trip.locations.find(params[:id])
end
# GET /locations/new
def new
trip = Trip.find(params[:trip_id])
#location = trip.locations.build
end
# GET /locations/1/edit
def edit
trip = Trip.find(params[:trip_id])
#2nd you retrieve the comment thanks to params[:id]
#location = trip.locations.find(params[:id])
end
# POST /locations
# POST /locations.json
def create
# #location = Location.new(location_params)
trip = Trip.find(params[:trip_id])
#location = trip.locations.create(params[:location_params])
respond_to do |format|
if #location.save
format.html { redirect_to trip_locations_url, notice: 'Location was successfully created.' }
format.json { render :show, status: :created, location: #location }
else
format.html { render :new }
format.json { render json: #location.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /locations/1
# PATCH/PUT /locations/1.json
def update
trip = Trip.find(params[:trip_id])
#location = trip.locations.find(params[:id])
respond_to do |format|
if #location.update(location_params)
format.html { redirect_to trip_locations_url, notice: 'Location was successfully updated.' }
format.json { render :show, status: :ok, location: #location }
else
format.html { render :edit }
format.json { render json: #location.errors, status: :unprocessable_entity }
end
end
end
# DELETE /locations/1
# DELETE /locations/1.json
def destroy
trip = Trip.find(params[:trip_id])
#location = trip.locations.find(params[:id])
#location.destroy
respond_to do |format|
format.html { redirect_to trip_locations_url, notice: 'Location was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_trip
trip = Trip.find(params[:trip_id])
end
# def set_location
# trip = Trip.find(params[:trip_id])
# #location = trip.locations.find(params[:id])
# # #location = Location.find(params[:id])
# end
# post = Post.find(params[:post_id])
# #2nd you retrieve the comment thanks to params[:id]
#
# #comment = post.comments.find(params[:id])
#
# Never trust parameters from the scary internet, only allow the white list through.
def location_params
params.require(:location).permit(:name, :image, :street_address_1, :street_address_2, :city, :state, :zip_code, :country_code, :trip_id)
end
end
Nav-tab
view/nav/_tabs.html.slim
- if can?(:read, #trip)
- controller = params[:controller].singularize
ul.nav.nav-tabs#tabs
li class=('active' if params[:action] == 'show')
= link_to 'Trip', trip_path(#trip)
li class=('active' if params[:action] == 'index')
= link_to 'Location', trip_locations_url(#trip)
li class=('active' if params[:action] == 'inex')
= link_to 'Members'
li class=('active' if params[:action] == 'index')
= link_to 'Events'
li class=('active' if params[:action] == 'index')
= link_to 'Status'
location index view
header.intro-location
.intro-body
.container
.row
.col-md-18
h1.brand-heading
= render 'nav/tabs'
h1.space-above Locations
-if can?(:create, #locations)
= link_to "Add New Location", new_trip_location_path, class: 'btn btn-xs btn-primary space-right'
table.table.table-striped-location.table-hover-location.space-above
thead
tr
th Name
th Image
th Street address 1
th Street address 2
th City
th State
th Zip code
th Country code
th
th
th
tbody
- #locations.each do |location|
tr
td = location.name
td = location.image
td = location.street_address_1
td = location.street_address_2
td = location.city
td = location.state
td = location.zip_code
td = location.country_code
td = link_to 'Show', trip_location_url(location.trip, location), class: 'btn btn-xs btn-success space-right'
td = link_to 'Edit', edit_trip_location_url(location.trip, location), class: 'btn btn-xs btn-primary space-right'
td = link_to 'Destroy', [location.trip, location], data: { confirm: 'Are you sure?' }, method: :delete, class: 'btn btn-xs btn-danger space-right'
trip show view
p#notice = notice
header.intro-location
.intro-body
.container
.row
.col-md-14
h3.brand-heading
= render 'nav/tabs'
.col-md-6.h4
table.table.table-striped-location.table-hover-location.space-above
thead
tr
th Name:
td.show = #trip.name
tr
th Description:
td.show = #trip.description
=> link_to 'Edit', edit_trip_path(#trip), class: 'btn btn-xs btn-primary space-right'
'|
=< link_to 'Back', trips_path, class: 'btn btn-xs btn-info space-right'
Updated the nav-bar
- if can?(:read, #trip)
- controller = params[:controller].singularize
ul.nav.nav-tabs#tabs
li class=('active' if params[:action] == 'show')
= link_to 'Trip', trip_path(#trip)
li class=('active' if params[:action] == 'index')
= link_to 'Location', trip_locations_url(#trip)
li class=('active' if params[:action] == 'inex')
= link_to 'Members'
li class=('active' if params[:action] == 'index')
= link_to 'Events'
li class=('active' if params[:action] == 'index')
= link_to 'Status'
Added #trip to location/index controller
class LocationsController < ApplicationController
def index
#trip = Trip.find(params[:trip_id])
trip = Trip.find(params[:trip_id])
#locations = trip.locations
# #locations = trip.locations
end

Failing RSpec test with strong parameters

I am migrating my Rails 4 app (still using protected attributes gem) to Rails 5.1.4. In the course of this action, I need to rewrite a lot of code to replace protected attributes with strong parameters.
I am currently stuck on one specific controller where my RSpec tests fail, and I don't know how to implement the controller and test logic such that things are correct and tests pass.
The app has an admin backend where users can add (and thus upload) photos to an album. The respective Admin::PhotosController handles the photos of an album.
Here's the relevant exerpt from my app:
def create
# #organizer_account is set by an before_filter
#album = #organizer_account.albums.find_by_id(params[:album_id])
#photo = #album.photos.new(photo_params)
#photo.organizer_account_id = #organizer_account.id
authorize! :create, #photo
respond_to do |format|
if #photo.save
format.html {
render :json => [#photo.to_jq_file].to_json, :content_type => 'text/html', :layout => false
}
format.json {
files = [ #photo.to_jq_file ]
render :json => {:files => [#photo.to_jq_file] }, :status => :created, :location => admin_album_photo_path(#album, #photo)
}
else
format.html {
render action: "new"
}
format.json {
render json: #photo.errors, status: :unprocessable_entity
}
end
end
end
I have defined the following strong parameters:
private
def photo_params
params.require(:photo).permit(:id, :album_id, :organizer_account_id, :file)
end
The failing RSpec test is as follows:
require 'spec_helper'
describe Admin::PhotosController, :type => :controller do
render_views
describe "post 'create'" do
describe "with valid parameters" do
before(:each) do
#organizer_account = FactoryBot.create(:organizer_account)
#user = FactoryBot.create(:user)
#user.organizer_account_id = #organizer_account.id
#user.add_role :admin, #organizer_account
#user.save
sign_in #user
#album = #organizer_account.albums.create(:title => "Album 1")
#photo_attrs = FactoryBot.attributes_for(:photo)
request.env["HTTP_REFERER"] = new_admin_album_path
controller.request.host = #organizer_account.subdomain + ".lvh.me"
end
it "should create a new photo record", :focus => true do
lambda {
post :create, params: {:photo => #photo_attrs, :album_id => #album.id }
}.should change(#organizer_account.albums.find_by_id(#album.id).photos, :count).by(1)
end
end
end
end
I strongly assume that the issue is in parameters are a) passed
post :create, params: {:photo => #photo_attrs, :album_id => #album.id }
and then processed
#photo = #album.photos.new(photo_params)
While the params hash passed by the test has all the required entries
params: {"photo"=><ActionController::Parameters {"file"=>[#<ActionDispatch::Http::UploadedFile:0x00000010dd7560 #tempfile=#<Tempfile:C:/Users/PATRIC~1/AppData/Local/Temp/RackMultipart20180520-11424-avge07.gif>, #original_filename="image6.gif", #content_type="image/gif", #headers="Content-Disposition: form-data; name=\"photo[file][]\"; filename=\"image6.gif\"\r\nContent-Type: image/gif\r\nContent-Length: 46844\r\n">]} permitted: false>, "album_id"=>"1561", "controller"=>"admin/photos", "action"=>"create"}
the photo_params is empty:
photo_params: {}
Update #1: Definition of factory for photo
FactoryBot.define do
factory :photo, :class => Photo do
file Rack::Test::UploadedFile.new(Rails.root + 'spec/fixtures/photos/apfelkuchen.jpg', "image/jpg")
end
end
Update #2: Photo model with file attachment and image processing config
class Photo < ActiveRecord::Base
require 'rmagick'
include Magick
belongs_to :album
belongs_to :organizer_account
before_destroy { |photo| photo.file.destroy }
validates :album_id, :presence => true
validates :organizer_account_id, :presence => true
has_attached_file :file,
# The following tyles and convert options lead to breaking RSpec tests. If commented, RSpec tests pass.
:styles => {
:mini => "50x50#",
:thumb => "160x160#",
:large => "1200x1200>"
},
:convert_options => {
:mini => "-quality 75 -strip",
:thumb => "-quality 75 -strip"
}
validates :file, :presence => true
end

Unable to edit multiple paperclip images in 1 form with Rails 4

I'm trying to upload multiple images with paperclip through 1 form, but I'm getting a Unpermitted parameters error.
This is my code:
Model:
class Recentjacket < ActiveRecord::Base
has_attached_file :jacketimage, :styles => { :medium => "300x300>", :thumb => "100x100>"}, :default_url => "/images/:style/missing.png"
end
Controller:
def recent
#recentjacket = Recentjacket.all
end
def update
params['recentjacket'].keys.each do |id|
#jacket = Recentjacket.find(id)
#jacket.update_attributes(recentjacket_params)
end
redirect_to '/recent'
end
private
def recentjacket_params
params.require(:recentjacket).permit(:jacketimage)
end
Html.slim
= form_for recent_path, html: { multipart: true } do |k|
- #recentjacket.each do |j|
= fields_for "recentjacket[]", j do |jacketfields|
= jacketfields.file_field :jacketimage
= k.submit "Update"
So basically there are 12 recentjackets in the database and when something is changed, it should overwrite the image.
Does anyone know how to fix this?
I fixed the problem:
def update
if params.has_key?(:jacket)
Recentjacket.update(params[:jacket].keys, params[:jacket].values)
redirect_to '/recent'
else
redirect_to '/recent/edit',
notice: 'No Files were selected to upload!'
end
end

ActiveRecord::RecordNotFound in UsersController#show

I'm just following Ruby on Rails 3 Tutorials (Mhartl) chapter-7 at the stage of 7.3.2 name and Gravatar.
Here I am facing a problem when I open on my browser it's says:
ActiveRecord::RecordNotFound in UsersController#show
Couldn't find User with id=1
Rails.root: C:/RubyOnRails/MyWorkPlace/sample_app_1
Application Trace | Framework Trace | Full Trace
app/controllers/users_controller.rb:5:in `show'
Request
Parameters:
{"id"=>"1"}
Show session dump
Show env dump
Response
Headers:
None
Also I pasted below User_controller.rb and user.rb
user.rb:
require 'digest'
class User < ActiveRecord::Base
attr_accessor :pasword
attr_accessible :login,
:username,
:email,
:password,
:password_confirmation,
:remember_me
email_regex = /\A[\w+\-.]+#[a-z\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :pasword, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
users_controller.rb:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#title = "Sign up"
end
end
Are you sure you created any user with id=1 ?
To check, go to rails console and get the user with id 1. If there is no user, then create one.
At firest, I see you have attr_accessor :pasword
I think it should be :password
Ontopic:
There are some actions missing in the restful controller, so it wont be possible to create a user.
See http://guides.rubyonrails.org/getting_started.html#rest for more details on RESTful controllers.
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#title = #user.name
end
def new
#user = User.new #this creates a empty user object to be filled with signup data
#title = "Sign up"
end
def create
#user = User.new(params[:user]) #this creates a new user object with the data you entered before.
if #user.save #if the data is valid, save it
redirect_to user_path(#user) #and go to the #user show action
else
render :action => :new #edit the invalid user data
end
end
def edit
#user = User.find(params[:id])
end
def update
#user = User.find(params[:id])
if #user.update_attributes(params[:user])
redirect_to user_url(#user)
else
render edit_user_url(#user)
end
end
def index
#users = User.all
end
def destroy
#user = User.find(params[:id]
#user.destroy
redirect_to :action => :index
end
end
edit: complete restful actions
I had the same problema. In my case, my 'redirect_to' on my detroy action was missin a 's' in 'posts_path'. It was post_path Noob, but worth i had checked up.
The reason you could not find the "user/1" is when you Added microposts to the sample data(db/seeds.rb) by typing
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
You forgot the "END" of the previous code, so the full picture of db/seeds.rb is
User.create!(name: "Example User",
email: "example#railstutorial.org",
password: "foobar",
password_confirmation: "foobar",
admin: true,
activated: true,
activated_at: Time.zone.now)
99.times do |n|
name = Faker::Name.name
email = "example-#{n+1}#railstutorial.org"
password = "password"
User.create!(name: name,
email: email,
password: password,
password_confirmation: password,
activated: true,
activated_at: Time.zone.now)
end
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end

Paperclip error processing the thumbnail

I have an error using Paperclip with Jcrop i follow a rails cast to make it.
When i upload an image it work but when i try to crop it, i get this inside my console :
[paperclip] An error was received while processing: #<Paperclip::PaperclipError: There was an error processing the thumbnail for paperclip-reprocess20110822-2281-19969sz>
[paperclip] An error was received while processing: #<Paperclip::PaperclipError: There was an error processing the thumbnail for paperclip-reprocess20110822-2281-19969sz>
There is my model :
class User < ActiveRecord::Base
has_attached_file :avatar, :styles => { :small => ["100x100#", :jpg], :large => ["500x500>",:jpg] }, :processors => [:cropper]
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
after_update :reprocess_avatar, :if => :cropping?
def cropping?
!crop_x.blank? && !crop_y.blank? && !crop_w.blank? && !crop_h.blank?
end
def avatar_geometry(style = :original)
#geometry ||= {}
#geometry[style] ||= Paperclip::Geometry.from_file(avatar.path(style))
end
private
def reprocess_avatar
avatar.reprocess!
end
end
And my processor :
module Paperclip
class Cropper < Thumbnail
def transformation_command
if crop_command
crop_command + super.first.sub(/ -crop \S+/, '')
else
super
end
end
def crop_command
target = #attachment.instance
if target.cropping?
" -crop #{target.crop_w}x#{target.crop_h}+#{target.crop_x}+#{target.crop_y}"
end
end
end
end
Any idea to solve this ? (I'm on OS 10.7)
Solution find inside the comments of the railscast
In the model
#has_attached_file :avatar, :styles => { :small => ["100x100#", :jpg], :large => ["500x500>",:jpg] }, :processors => [:cropper]
has_attached_file :avatar, :styles => {
:small => {:geometry => "100x100#", :processors => [:cropper]},
:large => {:geometry => "500x500>"}
}
In the processor
#crop_command + super.sub(/ -crop \S+/, '')
crop_command + super.join(' ').sub(/ -crop \S+/, '').split(' ')