Issue with uploading using CarrierWave_Direct - ruby-on-rails

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.

Related

Updating a referenced entity through the create form of another

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

Creating homes using nested routes

First this is all of my code
#models/user.rb
class User < ApplicationRecord
has_many :trips
has_many :homes, through: :trips
has_secure_password
accepts_nested_attributes_for :trips
accepts_nested_attributes_for :homes
validates :name, presence: true
validates :email, presence: true
validates :email, uniqueness: true
validates :password, presence: true
validates :password, confirmation: { case_sensitive: true }
end
#home.rb
class Home < ApplicationRecord
has_many :trips
has_many :users, through: :trips
validates :address, presence: true
end
class HomesController < ApplicationController
def show
#home = Home.find(params[:id])
end
def new
if params[:user_id]
#user = User.find_by(id: params[:user_id])
#home = #user.homes.build
end
end
def create
#user = User.find_by(id: params[:user_id])
binding.pry
#home = Home.new
end
private
def home_params
params.require(:home).permit(:address, :user_id)
end
end
I am trying to do something like this so that the home created is associated with the user that is creating it.
def create
#user = User.find_by(id: params[:user_id])
#home = Home.new(home_params)
if #home.save
#user.homes << #home
else
render :new
end
end
The problem is that the :user_id is not being passed into the params. So the #user comes out as nil. I can't find the reason why. Does this example make sense? Am I trying to set the associations correctly? Help or any insight would really be appreciated. Thanks in advance.
The way you would typically create resources as the current user is with an authentication such as Devise - not by nesting the resource. Instead you get the current user in the controller through the authentication system and build the resource off it:
resources :homes
class HomesController < ApplicationController
...
# GET /homes/new
def new
#home = current_user.homes.new
end
# POST /homes
def create
#home = current_user.homes.new(home_parameters)
if #home.save
redirect_to #home
else
render :new
end
end
...
end
This sets the user_id on the model (the Trip join model in this case) from the session or something like an access token when dealing with API's.
The reason you don't want to nest the resource when you're creating them as a specific user is that its trivial to pass another users id to create resources as another user. A session cookie is encrypted and thus much harder to tamper with and the same goes for authentication tokens.
by using if params[:user_id] and User.find_by(id: params[:user_id]) you are really just giving yourself potential nil errors and shooting yourself in the foot. If an action requires a user to be logged use a before_action callback to ensure they are authenticated and raise an error and bail (redirect the user to the sign in). Thats how authentication gems like Devise, Knock and Sorcery handle it.

ActionDispatch::Http::UploadedFile being passed as a string during integration test

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.

Uploaded images appear to save, then disappear later

I'm using Rails 4 to build a blog. Each blog post has an image, title and text. I can upload an image, see that the image is there when I look at the posts/:id page, but later when I go back to that same page the image is gone. I'm using the Paperclip gem for rails 4.
Is my image tied to a session somehow? Is it not really saving to the database? Here is a link to the deployed project with the image that doesn't show up: https://vinna.herokuapp.com/posts/1
I'm still learning, so any information is greatly appreciated!
Here is my controller:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:image, :title, :text)
end
end
My model:
class Post < ActiveRecord::Base
has_many :comments
has_attached_file :image, styles: { small: "100x100", med: "200x200", large: "600x600"}
validates :title, presence: true,
length: { minimum: 2 }
validates :text, presence: true,
length: { minimum: 2 }
validates_attachment_presence :image
validates_attachment_size :image, :less_than => 5.megabytes
validates_attachment_content_type :image, :content_type => ['image/jpeg', 'image/png']
end
My migrations:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :text
t.timestamps null: false
end
end
end
And adding paperclip:
class AddPaperclipToPost < ActiveRecord::Migration
def change
add_attachment :posts, :image
end
end
And part of my view from posts/:id
<p class="blog-photo_large"><%= link_to image_tag(#post.image.url(:large)), #post.image.url %></p>
This should work fine on single machine. However using heroku your app should be a 12-factor-app. In this case you shouldn't be using the Filesystem but an additional service for storing files. This is because the app code on heroku is distributed across multiple physical hardware instances and your never know which actual node will be responding to https://vinna.herokuapp.com/posts/1. So your first see the image on some particular node and then your are loadbalanced to some other which does not have it stored.
See point IV of The Twelve-Factor-App.

rails multiple belongs_to assignments with nested attributes

My problem is that how to assign multiple belongs_to associations with nested attributes?
I want to build a issue system. When I create a issue, I also want to create the first comment as the issue body.
So, I have following Models:
class Issue < ActiveRecord::Base
has_many :comments, as: :commentable
validates :title, presence: true
accepts_nested_attributes_for :comments
end
class Comment < ActiveRecord::Base
belongs_to :user
belongs_to :commentable, polymorphic: true
validates :content, :user, presence: true
end
and I have the IssuesController as follow:
class IssuesController < ApplicationController
before_action :authenticate_user! #devise authentication
def new
#issue = Issue.new
#issue.comments.build
end
def create
#issue = Issue.new(issue_params)
#issue.save
end
private
def issue_params
params.require(:issue).permit(:title, comments_attributes: [:content])
end
end
and the following is my form (using slim template with simple_form and nested_form gems):
= simple_nested_form_for #issue do |f|
= f.input :title
= f.fields_for :comments do |cf|
= cf.input :content
= f.button :submit
In this case, I don't know how to assign current_user to the comment created by nested attributes.
Any suggestions or other approaches? Thanks!
As I wrote in the comments, there's two ways of doing this.
The first way is to add a hidden field in your subform to set the current user:
= simple_nested_form_for(#issue) do |f|
= f.input :title
= f.fields_for(:comments) do |cf|
= cf.input(:content)
= cf.hidden(:user, current_user)
= f.submit
If you do not trust this approach in fear of your users fiddling with the fields in the browser, you can also do this in your controller.
class IssuesController < ApplicationController
before_action :authenticate_user! #devise authentication
def new
#issue = Issue.new
#issue.comments.build
end
def create
#issue = Issue.new(issue_params)
#issue.comments.first.user = current_user
#issue.save
end
private
def issue_params
params.require(:issue).permit(:title, comments_attributes: [:content])
end
end
This way you take the first comment that is created through the form and just manually assign the user to it. Then you know for the sure that the first created comment belongs to your current user.
You could also add user_id as current_user.id when you use params
class IssuesController < ApplicationController
before_action :authenticate_user! #devise authentication
def new
#issue = Issue.new
#issue.comments.build
end
def create
#issue = Issue.new(issue_params)
#issue.save
end
private
def issue_params
params[:issue][:comment][:user_id] = current_user.id
params.require(:issue).permit(:title, comments_attributes: [:content, :user_id])
end
end

Resources