Commit dbf8818b authored by Rafael Reggiani Manzo's avatar Rafael Reggiani Manzo

Merge branch 'send_content' into 'master'

Send content

Add form to send a video file and a feature to test video upload.
The file is uploaded to the local public/system folder.

See merge request !7
parents dcfaee50 8ea925d2
Pipeline #3599559 passed with stage
in 9 minutes and 59 seconds
......@@ -53,6 +53,9 @@ gem 'foundation-rails'
# A collection of CSS3 powered hover effects to be applied to links, buttons, logos, SVG, featured images and so on
gem 'hover-rails'
# Easy file attachment management for Active Record
gem 'paperclip', '~> 5.0.0.beta1'
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platform: :mri
......
......@@ -56,7 +56,11 @@ GEM
xpath (~> 2.0)
carmen (1.0.2)
activesupport (>= 3.0.0)
climate_control (0.0.3)
activesupport (>= 3.0)
cliver (0.3.2)
cocaine (0.5.8)
climate_control (>= 0.0.3, < 1.0)
coffee-rails (4.1.1)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.1.x)
......@@ -128,6 +132,7 @@ GEM
mime-types (3.1)
mime-types-data (~> 3.2015)
mime-types-data (3.2016.0521)
mimemagic (0.3.1)
mini_portile2 (2.1.0)
minitest (5.9.0)
multi_json (1.12.1)
......@@ -137,6 +142,12 @@ GEM
mini_portile2 (~> 2.1.0)
pkg-config (~> 1.1.7)
orm_adapter (0.5.0)
paperclip (5.0.0.beta2)
activemodel (>= 4.2.0)
activesupport (>= 4.2.0)
cocaine (~> 0.5.5)
mime-types
mimemagic (~> 0.3.0)
pkg-config (1.1.7)
poltergeist (1.9.0)
capybara (~> 2.1)
......@@ -274,6 +285,7 @@ DEPENDENCIES
jbuilder (~> 2.0)
jquery-rails
listen (~> 3.0.5)
paperclip (~> 5.0.0.beta1)
poltergeist
puma (~> 3.0)
rails (>= 5.0.0.rc1, < 5.1)
......
@video_zip_code = (event) ->
if $('input[name=video-has-location]').first().is(':checked')
$('#video-location').css('display', 'block')
$('#video-location').prop('required', true);
else
$('#video-location').css('display', 'none')
$('#video-location').prop('required', false)
$('input[name=video-has-location]').on('change', @video_zip_code)
class ContentsController < ApplicationController
before_action :set_content, only: [:show]
# GET /contents/1
# GET /contents/1.json
def show
end
# GET /contents/new
def new
@content = Content.new
end
# POST /contents
# POST /contents.json
def create
@content = Content.new(content_params)
@content.user_id = current_user.id
respond_to do |format|
if @content.save
format.html { redirect_to @content, notice: I18n.t('successfully_created_video') }
else
format.html { render :new }
end
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_content
@content = Content.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def content_params
params.require(:content).permit(:title, :description, :user_id, :adult, :rating, :soundtrack, :view_count, :deleted, :zip_code, :director, :co_director, :team, :allow_comments, :video)
end
end
module ContentsHelper
end
class Content < ApplicationRecord
has_attached_file :video
validates_attachment :video, presence: true
validates_attachment_content_type :video, content_type: /\Avideo\/.*\Z/
validates :terms_of_service, acceptance: true
validates :title, :user_id, :soundtrack, :director, presence: true
end
......@@ -13,4 +13,8 @@ class Profile < ApplicationRecord
def underaged?
!adult?
end
def full_name
"#{first_name} #{last_name}"
end
end
<div class="form-section">
<div class="columns small-12">
<h2><%= t('send_content') %></h2>
</div>
<%= form_for(content, id: 'content-upload-form', 'data-abide' => true, novalidate: true, html: { multipart: true } ) do |f| %>
<% if content.errors.any? %>
<div id="error_explanation" data-abide-error class="alert callout">
<h2 class="fa fa-exclamation-circle"><%= pluralize(content.errors.count, "error") %> prohibited this content from being saved:</h2>
<ul>
<% content.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<fieldset>
<div class="field-label">
<%= f.label "#{t('author')} *", for: 'content_author' %>
</div>
<div class="field-item">
<%= f.text_field :author, required: true, disabled: true, value: current_user.profile.full_name %>
</div>
</fieldset>
<!-- TODO: Add contests for content upload
<fieldset class="contest-select">
<div class="field-label">
<legend>
Concurso *
<span data-tooltip aria-haspopup="true" class="has-tip top" data-disable-hover='false' tabindex=1 title="Você pode incluir o seu vídeo em mais de um concurso, caso ele se encaixe no tema"><i class="fa fa-question-circle"></i></span>
</legend>
</div>
<div class="field-item">
<input type="checkbox" name="contest-select" id="contest-1"><label for="contest-1">Concurso 1 <a href="#" title="Ver concurso" target="_blank"><i class="fa fa-external-link"></i></a></label><br />
<input type="checkbox" name="contest-select" id="contest-2"><label for="contest-2">Concurso 2 <a href="#" title="Ver concurso" target="_blank"><i class="fa fa-external-link"></i></a></label><br />
<input type="checkbox" name="contest-select" id="contest-3"><label for="contest-3">Concurso 3 <a href="#" title="Ver concurso" target="_blank"><i class="fa fa-external-link"></i></a></label><br />
<input type="checkbox" name="contest-select" id="contest-4"><label for="contest-4">Concurso 4 <a href="#" title="Ver concurso" target="_blank"><i class="fa fa-external-link"></i></a></label>
</div>
</fieldset>
-->
<fieldset>
<div class="field-label-item">
<%= f.check_box :terms_of_service, id: 'user-terms', required: true %>
<%= f.label t('terms_of_service_html'), for: :terms_of_service %>
</div>
</fieldset>
<div id="content-upload-field-group">
<div class="content-field-group video-field">
<fieldset>
<div class="field-label-item">
<h3><%= t('video') %></h3>
</div>
<div class="field-label">
<%= f.label "#{t('video')} *", for: 'video-upload' %>
</div>
<div class="field-item">
<%= f.label t('select_file'), for: 'video-upload', class: 'button' %>
<%= f.file_field :video, required: true, class: 'show-for-sr', id: 'video-upload' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label "#{t('title')} *", for: 'video-title' %>
</div>
<div class="field-item">
<%= f.text_field :title, required: true, id: 'video-title' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label t('description'), for: 'video-description' %>
</div>
<div class="field-item">
<%= f.text_field :description, id: 'video-description' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label "#{t('director')} *", for: 'video-director' %>
</div>
<div class="field-item">
<%= f.text_field :director, required: true, id: 'video-director' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label t('co_director'), for: 'video-co-director' %>
</div>
<div class="field-item">
<%= f.text_field :co_director, id: 'video-co-director' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label(t('team'), for: 'video-team') do %>
<span data-tooltip aria-haspopup="true" class="has-tip top" data-disable-hover="false" tabindex=1 title="Explicação sobre o campo"><i class="fa fa-question-circle"></i></span>
<% end %>
</div>
<div class="field-item">
<%= f.text_area :team, id: 'video-team' %>
</div>
</fieldset>
<fieldset>
<div class="field-label">
<%= f.label("#{t('soundtrack')} *", for: 'video-soundtrack') do %>
<span data-tooltip aria-haspopup="true" class="has-tip top" data-disable-hover="false" tabindex=1 title="Explicação sobre o campo"><i class="fa fa-question-circle"></i></span>
<% end %>
</div>
<div class="field-item">
<%= f.text_field :soundtrack, id: 'video-soundtrack', required: true %>
</div>
</fieldset>
<fieldset>
<div class="field-label-item">
<%= f.label(t('specific_location'), for: 'video-has-location') do %>
<%= check_box_tag 'video-has-location', 'yes' %>
<% end %>
</div>
</fieldset>
<fieldset style="display: none;" id="video-location">
<div class="field-label">
<%= f.label "#{t('zip_code')} *", for: 'video-location-postal-code' %>
</div>
<div class="field-item">
<%= f.text_field :zip_code, id: 'video-location-postal-code', placeholder: '99999-999' %>
</div>
</fieldset>
<!-- TODO: Add tag support for contents
<fieldset>
<div class="field-label">
<label for="video-tags">Tags</label>
</div>
<div class="field-item">
<input type="text" id="video-tags" name="video-tags" placeholder="" aria-describedby="tagsHelpText">
<span class="form-error">
Campo obrigatório.
</span>
<p class="help-text" id="tagsHelpText">Utilize vírgulas entre as tags</p>
</div>
</fieldset>
-->
<fieldset>
<div class="field-label-item">
<%= f.label(t('adult'), for: 'video-adults-only') do %>
<%= f.check_box :adult, id: 'video-adults-only', value: 'yes' %>
<% end %>
</div>
</fieldset>
<fieldset>
<div class="field-label-item">
<%= f.label(t('allow_commentary'), for: 'video-allow-commentary') do %>
<%= f.check_box :allow_comments, id: 'video-allow-commentary', value: 'yes' %>
<% end %>
</div>
</fieldset>
</div>
</div>
<fieldset>
<div class="field-label-item">
<%= f.submit t('register'), class: 'button' %>
</div>
</fieldset>
<% end %>
</div>
<% content_for :footer do %>
<%= javascript_include_tag 'contents/show_zip_code_input.js' %>
<% end %>
<h1>New Content</h1>
<%= render 'form', content: @content %>
<%= link_to 'Back', contents_path %>
<p id="notice"><%= notice %></p>
<p>
<strong>Title:</strong>
<%= @content.title %>
</p>
<p>
<strong>Description:</strong>
<%= @content.description %>
</p>
<p>
<strong>User:</strong>
<%= @content.user_id %>
</p>
<p>
<strong>Adult:</strong>
<%= @content.adult %>
</p>
<p>
<strong>Rating:</strong>
<%= @content.rating %>
</p>
<p>
<strong>Soundtrack:</strong>
<%= @content.soundtrack %>
</p>
<p>
<strong>View count:</strong>
<%= @content.view_count %>
</p>
<p>
<strong>Deleted:</strong>
<%= @content.deleted %>
</p>
<p>
<strong>Zip code:</strong>
<%= @content.zip_code %>
</p>
<p>
<strong>Director:</strong>
<%= @content.director %>
</p>
<p>
<strong>Co director:</strong>
<%= @content.co_director %>
</p>
<p>
<strong>Team:</strong>
<%= @content.team %>
</p>
<p>
<strong>Allow comments:</strong>
<%= @content.allow_comments %>
</p>
<%= link_to 'Back', contents_path %>
......@@ -24,6 +24,9 @@ chdir APP_ROOT do
# Add necessary setup steps to this file.
green '== Installing dependencies =='
# Check for ImageMagick convert
system! 'which convert'
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
......@@ -40,6 +43,6 @@ chdir APP_ROOT do
green '== Restarting application server =='
system! 'bin/rails restart'
green 'All done!'
end
......@@ -9,4 +9,4 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
# Rails.application.config.assets.precompile += %w( search.js )
Rails.application.config.assets.precompile += %w( profiles/*.js )
Rails.application.config.assets.precompile += %w( profiles/*.js contents/*.js )
en:
successfully_created_video: Successfully created video
pt-BR:
successfully_created_video: Video criado com sucesso
......@@ -3,6 +3,7 @@ Rails.application.routes.draw do
devise_for :users
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
resources :profiles, except: [:index]
resources :contents, only: [:new, :create, :show]
get 'states' => 'profiles#states', as: 'states'
......
class CreateContents < ActiveRecord::Migration[5.0]
def change
create_table :contents do |t|
t.string :title, null: false
t.string :description
t.integer :user_id, null: false
t.boolean :adult, default: false, null: false
t.integer :rating
t.string :soundtrack, null: false
t.integer :view_count, default: 0
t.boolean :deleted, default: false, null: false
t.string :zip_code
t.string :director, null: false
t.string :co_director
t.string :team
t.boolean :allow_comments, default: true
t.timestamps
end
end
end
class AddAttachmentVideoToContents < ActiveRecord::Migration[5.0]
def self.up
change_table :contents do |t|
t.attachment :video
end
end
def self.down
remove_attachment :contents, :video
end
end
......@@ -13,6 +13,28 @@
ActiveRecord::Schema.define(version: 20160624142640) do
create_table "contents", force: :cascade do |t|
t.string "title", null: false
t.string "description"
t.integer "user_id", null: false
t.boolean "adult", default: false, null: false
t.integer "rating"
t.string "soundtrack", null: false
t.integer "view_count", default: 0
t.boolean "deleted", default: false, null: false
t.string "zip_code"
t.string "director", null: false
t.string "co_director"
t.string "team"
t.boolean "allow_comments", default: true
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "video_file_name"
t.string "video_content_type"
t.integer "video_file_size"
t.datetime "video_updated_at"
end
create_table "profiles", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
......
Feature: Send Content
In order to be able to see video contents
As a regular user
I should be able to send my own video
Background:
Given I have a registered and confirmed user
And I am logged in
And I have a video to be uploaded
@javascript
Scenario: Uploading a valid file
Given I have a complete profile
And I am at the send content page
And I see my name on the Author field
When I fill the title field with the video's title
And I mark the terms of service checkbox
And I fill the video's soundtrack
And I fill the director field with my name
And I mark the specific location checkbox
And I fill the zip code field with my zip code
And I send a video file
And I click on the register button
Then I should see the successful upload message
Given(/^I have a complete profile$/) do
@profile = create(:profile, user_id: @user.id)
end
Given(/^I am at the send content page$/) do
visit new_content_path
end
Given(/^I see my name on the Author field$/) do
expect(find('#content_author').value).to eq @profile.full_name
end
Given(/^I have a video to be uploaded$/) do
@video_attributes = attributes_for :video, :cucumber
end
When(/^I fill the title field with the video's title$/) do
step "I fill the \"video-title\" field with \"#{@video_attributes[:title]}\""
end
When(/^I fill the video's soundtrack$/) do
step "I fill the \"video-soundtrack\" field with \"#{@video_attributes[:soundtrack]}\""
end
When(/^I fill the director field with my name$/) do
step "I fill the \"video-director\" field with \"#{@profile.full_name}\""
end
When(/^I mark the specific location checkbox$/) do
step "I check the \"video-has-location\" box"
end
When(/^I fill the zip code field with my zip code$/) do
step "I fill the \"video-location-postal-code\" field with \"#{@video_attributes[:zip_code]}\""
end
When(/^I send a video file$/) do
attach_file 'video-upload', @video_attributes[:video]
end
When(/^I click on the register button$/) do
step "I click on the \"#{I18n.t 'register'}\" button"
end
Then(/^I should see the successful upload message$/) do
step "I should see \"#{I18n.t 'successfully_created_video'}\""
end
require 'rails_helper'
# This spec was generated by rspec-rails when you ran the scaffold generator.
# It demonstrates how one might use RSpec to specify the controller code that
# was generated by Rails when you ran the scaffold generator.
#
# It assumes that the implementation code is generated by the rails scaffold
# generator. If you are using any extension libraries to generate different
# controller code, this generated spec may or may not pass.
#
# It only uses APIs available in rails and/or rspec-rails. There are a number
# of tools you can use to make these specs even more expressive, but we're
# sticking to rails and rspec-rails APIs to keep things simple and stable.
#
# Compared to earlier versions of this generator, there is very limited use of
# stubs and message expectations in this spec. Stubs are only used when there
# is no simpler way to get a handle on the object needed for the example.
# Message expectations are only used when there is no simpler way to specify
# that an instance is receiving a specific message.
RSpec.describe ContentsController, type: :controller do
include Devise::TestHelpers
let(:valid_request_parameters) {
attributes_for(:video)
}
let(:user) { create(:user, confirmed_at: Time.zone.now) }
before do
sign_in user
end
describe 'GET #show' do
let(:video) { build(:video, :rspec, id: 1) }
context 'video found' do
before do
expect(Content).to receive(:find).with(video.id.to_s).and_return(video)
get :show, params: { id: video.id }
end
it { is_expected.to respond_with(:ok) }
end
context 'video not found' do
before do
expect(Content).to receive(:find).with(video.id.to_s).and_raise ActiveRecord::RecordNotFound
end
it 'responds with not found' do
pending 'Handle not found errors'
get :show, params: { id: video.id }
it { is_expected.to respond_with(:not_found) }
end
end
end
describe 'GET #new' do
it 'responds with ok' do
get :new
expect(response.status).to eq 200
end
end
describe 'POST #create' do
let(:video) { build(:video, with: :rspec) }
context 'with valid params' do
before do
expect_any_instance_of(Content).to receive(:save).and_return true
post :create, params: { content: valid_request_parameters }
end
it 'redirects to the created content' do
expect(response).to redirect_to(Content.last)
end
end
context 'with invalid params' do
before do
expect_any_instance_of(Content).to receive(:save).and_return false
post :create, params: { content: valid_request_parameters }
end
it { is_expected.to respond_with(:ok) }
end
end
end
FactoryGirl.define do
factory :video, class: Content do
title 'Video Title'
zip_code '00000-000'
soundtrack 'soundtrack'
end
trait :rspec do
video_file_name "#{Rails.root}/public/cucumber/videos/video.mp4"
end
trait :cucumber do
video "#{Rails.root}/public/cucumber/videos/video.mp4"
end
trait :zencoder do
video { File.new "#{Rails.root}/public/cucumber/videos/video.mp4" }
end
end
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the ContentsHelper. For example:
#
# describe ContentsHelper do
# describe "string concat" do
# it "concats two strings with spaces" do
# expect(helper.concat_strings("this","that")).to eq("this that")
# end
# end
# end
RSpec.describe ContentsHelper, type: :helper do
pending "add some examples to (or delete) #{__FILE__}"
end
#= require spec_helper
#= require contents/show_zip_code_input
describe 'show_zip_code_input', ->
before ->
@first_mock = is: ->
@location = {
css: ->,
prop: ->
}
@location_inputs = first: ->
sinon.stub(window, '$')
$.withArgs('input[name=video-has-location]').returns(@location_inputs)
$.withArgs('#video-location').returns(@location)
@location_mock = sinon.mock(@location)
@first_stub = sinon.stub(@location_inputs, 'first').returns(@first_mock)
describe 'when the location input is checked', ->
before ->
@checked = true
@is_stub = sinon.stub(@first_mock, 'is').withArgs(':checked').returns(@checked)
it 'shows the location fields', ->
@location_mock.expects('css').once().withArgs('display', 'block')
@location_mock.expects('prop').once().withArgs('required', true)
video_zip_code()
sinon.assert.called(@first_stub)
sinon.assert.called(@is_stub)
after ->
@first_mock.is.restore()
describe 'when the location input is not checked', ->
before ->