I have an Album model and a child Picture model, whereby a file upload is handled by carrierwave.
The app runs as intended, however fails when I try to write an integration test for the upload.
From the development log, working parameters for this appear as follows:
Parameters: ..., "images"=>[#<ActionDispatch::Http::UploadedFile:0x007f8a42be2c78>...
During the integration test, im getting parameters as follows:
Parameters: ..., "images"=>["#<ActionDispatch::Http::UploadedFile:0x00000004706fd0>"...
As you can see, in the test scenario, #<ActionDispatch::Http::UploadedFile is being passed through as a string (confirmed with byebug), which is causing the test to fail.
How do I get the test to pass this as the object?
Integration Test
require 'test_helper'
class AlbumsCreationTest < ActionDispatch::IntegrationTest
def setup
#user = users(:archer)
#file ||= File.open(File.expand_path(Rails.root + 'test/fixtures/cat1.jpg', __FILE__))
#testfile ||= uploaded_file_object(PicturesUploader, :image, #file)
end
test "should create new album with valid info" do
log_in_as(#user)
assert_difference 'Album.count', 1 do #This assertion fails
post user_albums_path(#user), album: { title: 'test',
description: 'test',
}, images: [#testfile]
end
assert_redirected_to #user
end
end
from test_helper.rb
# Upload File (Carrierwave) Ref: http://nicholshayes.co.uk/blog/?p=405
def uploaded_file_object(klass, attribute, file, content_type = 'image/jpeg')
filename = File.basename(file.path)
klass_label = klass.to_s.underscore
ActionDispatch::Http::UploadedFile.new(
tempfile: file,
filename: filename,
head: %Q{Content-Disposition: form-data; name="#{klass_label}[#{attribute}]"; filename="#{filename}"},
type: content_type
)
end
Models
class Album < ActiveRecord::Base
belongs_to :user
has_many :pictures, dependent: :destroy
accepts_nested_attributes_for :pictures, allow_destroy: true
validates_presence_of :pictures
end
class Picture < ActiveRecord::Base
belongs_to :album
mount_uploader :image, PicturesUploader
validates_integrity_of :image
validates_processing_of :image
validates :image, presence: true,
file_size: { less_than: 10.megabytes }
end
Controller
class AlbumsController < ApplicationController
before_action :valid_user, only: [:new, :create, :edit, :update, :destroy]
def create
#album = current_user.albums.build(album_params)
if params[:images]
params[:images].each { |file|
debugger #file.class is String in integration test
#album.pictures.build(image: file)
}
end
if #album.save
# end
flash[:success] = "Album Created!"
redirect_to current_user
else
flash[:alert] = "Something went wrong."
render :new
end
end
private
def album_params
params.require(:album).permit(:user_id, :title, :description, :price,
pictures_attributes: [:id, :image, :image_cache, :_destroy])
end
def valid_user
#user = User.friendly.find(params[:user_id])
redirect_to(root_url) unless #user == current_user
end
end
Solved:
It appears that the uploaded_file_object method doesn't work at the integration test level.
I went back to using fixture_file_upload and everything works as intended.
Related
I have 2 models that are linked through a joint table:
class Dailyreport < ApplicationRecord
max_paginates_per 9
belongs_to :owner
has_many :dailyreport_issues
has_many :issues, through: :dailyreport_issues
accepts_nested_attributes_for :issues, allow_destroy: true
end
class Issue < ApplicationRecord
belongs_to :project
belongs_to :owner
has_many :dailyreport_issues
has_many :dailyreports, through: :dailyreport_issues
max_paginates_per 10
before_create { |issue| issue.jiraid = issue.jiraid.upcase }
validates :jiraid, uniqueness: true
validates :jiraid, :project, :owner, :time_forecast, :time_real, presence: true
validates :jiraid, format: { with: /\b[a-zA-Z]{2,6}-[1-9]\d{0,3}\b/, message: 'must follow this format ABCXYZ-9999' }
validates :time_real, numericality: { only_float: true }
validates :time_forecast, numericality: { only_float: true }
end
class DailyreportIssue < ApplicationRecord
belongs_to :dailyreport
belongs_to :issue
end
I use nested forms 'cocoon gem' to generate issues inside the create form of the dailyreport.
I successfully implemented that with these 2 controllers:
class DailyreportsController < ApplicationController
helper DailyreportsHelper
before_action :define_dailyreport, only: [:edit, :show, :update, :destroy]
def index
#dailyreports = Dailyreport.all.order(created_at: :desc).page params[:page]
end
def new
#dailyreport = Dailyreport.new
#dailyreport.issues.build
#issues = Issue.all.order(created_at: :desc)
end
def edit
end
def show
end
def owner_dailyreport
#owner_dailyreport = current_user.owner.dailyreports
end
def create
#dailyreport = Dailyreport.new(dailyreport_params)
#dailyreport.issues.each do |cr_issue|
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{cr_issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{cr_issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(cr_issue)
issue_time_real_from_jira(cr_issue)
end
if #dailyreport.save!
redirect_to #dailyreport, notice: 'Dailyreport was successfully created.'
else
render :new
end
end
end
def update
if #dailyreport.update(dailyreport_params)
redirect_to #dailyreport, notice: 'Dailyreport was successfully updated.'
else
render :edit
end
end
def destroy
if current_user.admin? || current_user.email == #dailyreport.owner.email
#dailyreport.destroy
else
admin_only_access
end
previous_page
end
private
def dailyreport_params
params.require(:dailyreport).permit(
:comment,
:owner_id,
issues_attributes: [
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status,
:_destroy
]
)
end
def define_dailyreport
#dailyreport = Dailyreport.find(params[:id])
end
end
class IssuesController < ApplicationController
require 'net/http'
require 'uri'
before_action :define_issue, only: [:show, :edit, :update, :destroy]
before_action :admin_only_access, only: [:destroy, :edit, :update]
def index
#issues = Issue.all.order(created_at: :desc).page params[:page]
end
def search
if params[:search].blank?
redirect_to issues_path and return
else
#parameter = params[:search].downcase
#results = Issue.all.where('lower(jiraid) LIKE :search', search: "%#{#parameter}%").page params[:page]
end
end
def new
#issue = Issue.new
end
def show
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
no_api_reponse
else
issue_details_from_jira(#issue)
yes_api_response
end
end
def create
#issue = Issue.new(issue_params)
# Check if issue exists on JIRA
unless call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{#issue.jiraid}")
flash.alert = "Please check if #{#issue.jiraid} exists and is available on JIRA"
end
# Get issue details from JIRA
issue_details_from_jira(#issue)
issue_time_real_from_jira(#issue)
# Save the issue
if #issue.save
flash.notice = "Issue #{#issue.jiraid} created"
redirect_to issues_path and return
else
flash.alert = "There was a problem saving #{#issue.jiraid}, check if all the fields are filled on the JIRA issue"
end
end
def edit
end
def update
if #issue.update(issue_params)
redirect_to issues_path
else
render :edit, status: :unprocessable_entity
end
end
def destroy
if current_user.admin?
#issue.destroy
else
admin_only_access
end
previous_page
end
private
def issue_params
params.require(:issue).permit(
:jiraid,
:project_id,
:owner_id,
:time_forecast,
:time_real,
:departement,
:retour_test,
:status
)
end
def define_issue
#issue = Issue.find(params[:id])
#issue_owner = Owner.find_by(params[:current_user])
end
end
My routesRails.application.routes.draw do
get '/search', to: 'issues#search'
get '/home/jira', to: 'home#jira'
get '/dailyreports/owner_dailyreport/:id', to: 'dailyreports#owner_dailyreport', :as => 'my_crs'
resources :projects
resources :issues
resources :departements
resources :owners
resources :dailyreports
# Devise routes
devise_scope :user do
get 'users', to: 'devise/sessions#new'
end
devise_for :users
authenticated :user do
root to: 'home#index', as: :authenticated_root
end
root to: redirect('/users/sign_in')
end
I am trying to implement an update or create process:
Check if the JIRAID exists in my DB
If it doesn't just get the data and save the dailyreport.
If it does, I call the API and get its updated details then update it and save the dailyreport.
And here I found some issues with the code I tried.
First when I update the issue then try to save the dailyreport, it throws the validation error (Jiraid exists) because the dailyreport.save is trying to update the issue again.
I also tried this:
def create
#dailyreport = Dailyreport.new(dailyreport_params)
issues_attributes = params[:dailyreport][:issues_attributes]
p("///////////////////////////////////ISSUES_ATTRIBUTES#{issues_attributes}")
issues_attributes.each do |_, issue_attributes|
p("~~~~~~~~~~~~~~~~~~~~~~ISSUE_ATTRIBUTE#{issue_attributes}")
# Call the JIRA API and check for errors
call_jira_api("https://agenceinspire.atlassian.net/rest/api/3/issue/#{issue_attributes["jiraid"]}")
if #response_output_issues.key?('errors')
flash.alert = "Please check if #{issue_attributes["jiraid"]} exists and is available on JIRA"
return
end
# Update the issue attributes with details from the JIRA API
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
p("~~~~~~~~~~~~~~~~~~~~~~JIRA ID IN THE DB: #{issue.jiraid}")
# Check if the issue already exists in the database
issue = Issue.find_by(jiraid: issue_attributes["jiraid"])
if issue
issue_details_from_jira(issue)
issue_time_real_from_jira(issue)
# Update the existing issue
issue.update(
time_forecast: issue.time_forecast,
time_real: issue.time_real,
status: issue.status
)
else
# Build and save a new issue if it doesn't exist
#dailyreport.issues.build(issue_attributes)
end
end
I know I have an issue here:
issue_details_from_jira(issue_attributes)
issue_time_real_from_jira(issue_attributes)
I am going to have to create an object to pass to my methods. But i don't know how.
I couldn't update the issue from the dailyreport controller too, so I tried passing the update method (+ the id) inside the strong params of the dailyreport. That resulted in a ForbiddenAttributes error.
I actually need a lead of how to approach this, not a specific solution. I think that my approach is wrong.
thank you in advance
So I have these files
deal.rb
class Deal < ApplicationRecord
has_many :images, as: :imageable, dependent: :destroy
#there is more code after this
end
image.rb
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
deals_controller.rb
module Admins
class DealsController < BaseController
before_action :find_deal, only: [:edit, :update, :destroy]
def index
#deals = Deal.includes(:images)
end
def new
#deal = Deal.new
end
def edit
end
def create
#deal = Deal.new(deal_params.merge(created_by: current_user.id))
if #deal.save
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :new
end
end
def update
if #deal.update(deal_params)
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = #deal.errors[:base].to_sentence
render :edit
end
end
def destroy
if #deal.destroy
flash[:success] = t('.success')
redirect_to admins_deals_url
else
flash.now[:warning] = t('.failure')
render :index
end
end
private
def deal_params
params.require(:deal).permit(:title, :description, :price, :discounted_price, :quantity, :publish_date, images_attributes: [:id, :attachment, :_destroy])
end
def find_deal
#deal = Deal.find_by(id: params[:id])
unless #deal
flash[:warning] = t('deals.not_found')
redirect_to admins_deals_path
end
end
end
end
application_controller.rb
class ApplicationController < ActionController::Base
helper_method :current_user, :current_cart
def current_user
#current_user ||= User.find_by(id: current_user_id)
end
def current_user_id
cookies.signed[:user_id] || session[:user_id]
end
def current_cart
#current_cart ||= (current_user.addressed_cart || current_user.cart) if current_user
end
end
EDIT:
Although I don't think application_controller has anything to do with the error
I am creating a deal with nested image attributes. I am using paperclip to upload the images. But I am getting these errors. I don't have any idea what the errors even mean. Here is an image to show the errors.
Here is the pastebin link
errors on terminal on creating deal
This appears to be a validation error. Try this for your validation:
validates_attachment_content_type :attachment, :content_type => /image/
Or for other variations you can see Validate Attachment Content Type Paperclip
UPDATE after testing your code seems this was a validation error because Paperclip creates an image but doesn't know about the belongs_to association. You can make it optional because by default rails 5 requires the belongs_to id field.
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :deal, optional: true
has_attached_file :attachment, styles: { thumb: "100x100!", medium: "200x200!" }
validates_attachment_content_type :attachment, content_type: /\Aimage\/.*\z/
end
I recently created a profile page for the users of the app am developing but along the way i noticed that the URL for the story originally is a little different from the same story on the users page. e.g normal url is "http://localhost:3000/genres/action/stories/37" while it is "http://localhost:3000/genres/absurdist/stories/37", meanwhile the story originally belongs to "action genre" and not "absurdist". Meanwhile, the two urls directs to the normal story page.
genre.rb
class Genre < ApplicationRecord
belongs_to :user
has_many :stories, dependent: :destroy
is_impressionable
extend FriendlyId
friendly_id :name, use: :slugged
def should_generate_new_friendly_id?
name_changed?
end
end
story.rb
class Story < ApplicationRecord
belongs_to :genre
belongs_to :user
has_many :episodes, dependent: :destroy
is_impressionable
extend FriendlyId
friendly_id :title, use: :slugged
def should_generate_new_friendly_id?
title_changed?
end
has_attached_file :image, size: { less_than: 1.megabyte }, styles: { medium: "300x300#", wide: "200x400#" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
scope :of_followed_users, -> (following_users) { where user_id: following_users }
end
profiles_controller
class ProfilesController < ApplicationController
before_action :find_user
before_action :find_genre
before_action :owned_profile, only: [:edit, :update]
before_action :authenticate_user!
def show
#stories = User.find_by(user_name: params[:user_name]).stories.order('created_at DESC')
impressionist(#user)
end
def edit
end
def update
if #user.update(profile_params)
flash[:success] = 'Your profile has been updated.'
redirect_to profile_path(#user.user_name)
else
#user.errors.full_messages
flash[:error] = #user.errors.full_messages
render :edit
end
end
private
def profile_params
params.require(:user).permit(:avatar, :bio)
end
def find_user
#user = User.find_by(user_name: params[:user_name])
end
def find_genre
#genre = Genre.friendly.find_by(params[:slug])
end
def owned_profile
#user = User.find_by(user_name: params[:user_name])
unless current_user == #user
flash[:alert] = "That profile does not belong to you"
redirect_to root_path
end
end
end
profiles show
<div class="container my-5">
<div class="card-columns clearfix">
<%= render #user.stories %>
</div>
</div>
This POST create test is not working. It should redirect to reports_path, but it does not work.
describe "POST #create" do
let(:report) { assigns(:report) }
let(:test_option) { create(:option) }
let(:test_student) { create(:student) }
context "when valid" do
before(:each) do
post :create, params: {
report: attributes_for(:report, student: test_student,
report_options_attributes: [build(:report_option).attributes]
)
}
end
it "should redirect to reports_path" do
expect(response).to redirect_to reports_path
end
My params are set like this:
def report_params
params.require(:report).permit(:student,
report_options_attributes: [:id, :option, :note, :_destroy]
)
end
Controller:
def create
#report = Report.new(report_params)
if #report.save
redirect_to reports_path
else
render :new
end
end
Report model:
class Report < ApplicationRecord
belongs_to :student, dependent: :delete
has_many :report_options, dependent: :destroy
accepts_nested_attributes_for :report_options, allow_destroy: true
end
ReportOption model:
class ReportOption < ApplicationRecord
belongs_to :option
belongs_to :report, optional: true
end
I am passing the correct params in the test, but I really do not know what is going wrong..
I've had a similar problem. I fixed it passing just the ids in the params like that:
def report_params
params.require(:report).permit(:student_id,
report_options_attributes: [:id, :option_id, :note, :_destroy]
)
end
Don't know if it will work for you. You also need to change the student: test_student to student_id: test_student.id inside your before(:each).
I'm pretty new to ruby and rails and I'm having some problems uploading using carrierwave_direct. I followed Ryan Bates' Railscast 383 on uploading using carrierwave_direct. However, the solution he provides doesn't seem to be working for me. I have a User model that I created using Devise and I have an uploader that handles my uploading. I am trying to use the uploader to upload video files which I plan to later transcode using the Elastic Transoder with the AWS API. The files are uploading fine to s3, but it appears that they are not getting associated with a record in my database.The files upload as soon as I submit them, but I need a way to create them along with a title, a description, and potentially other parameters later. My if-else statement always redirects to my else statement as well. I think my problem is in my controller; my routes and views seem to be working fine, I'm just stuck on this one issue.
To clarify further: I need users to be able to upload a video and then have the user input a title, description, etc. and then be redirected to another page. I then need to be able to show that file later when called upon.
Here is my UploadsController:
class UploadsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
before_action :current_user, only: :destroy
def index
#uploader = Upload.new.video
#uploader.success_action_redirect = new_uploads_url
end
def show
#upload = Upload.find(params[:id])
end
def new
#upload = Upload.new(key: params[:key])
end
def create
#upload = Upload.new(upload_params)
if #upload.save
redirect_to home_index_path
return
else
redirect_to uploads_index_path
end
end
def edit
end
def destroy
#upload = Upload.find(params[:id])
#upload.destroy
flash[:notice] = "Upload deleted"
redirect_to request.referrer || root_url
end
def upload_params
params.require(:upload).permit(:video, :title, :description)
end
end
Here is my Upload Model:
class Upload < ActiveRecord::Base
belongs_to :user
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :description, presence: true
validates :title, presence: true, length: {maximum: 100}
validates :description, presence: true
validates :video, presence: true
mount_uploader :video, VideoUploader
after_save :enqueue_video
def enqueue_video
VideoWorker.perform_async(id, key) if key.present?
end
class VideoWorker
include Sidekiq::Worker
def perform(id, key)
upload = Upload.find(id)
upload.key = key
video.remote_video_url = upload.video.direct_fog_url(with_path: true)
upload.save!
end
end
end
And my routes:
Rails.application.routes.draw do
get '/home/index'
root 'home#index'
devise_for :users
get 'users/:id' => 'users#show'
resources :uploads
get '/index' => 'uploads#index'
end
EDIT Here is my UsersController and User Show Page as well:
Controller:
class UsersController < ApplicationController
def show
#user = User.find(params[:id])
end
end
Show:
<h1><%= #user.username %></h1>
<h3>Title:</h3>
<%= #upload.title %>
The problem is that you're doing:
def create
#upload = Upload.new(upload_params)
# [..]
end
def upload_params
params.require(:upload).permit(:video, :title, :description)
end
And that your Upload is linked to a User with:
class Upload < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
# [..]
end
So how does Rails know which Upload belongs to which User? You never told Upload anything 'bout no User!
If you always want to use the currently logged in user, you could probably do something like:
def create
#upload = Upload.new(upload_params)
#upload.user = current_user
# [...]
end
This assumes, of course, that current_user is the correct method to get the currently logged in user (it usually is).
If you want to be able to connect it to any other user, you need to add a user_id field in the and add it to upload_params.