Save track files in new Sound model:
* Consolidate migrations * Add Sound model. Each sound can belong to a track and contains informations about one sound file.
This commit is contained in:
parent
f49a5a3f67
commit
27550fd14e
6
app/controllers/sounds_controller.rb
Normal file
6
app/controllers/sounds_controller.rb
Normal file
@ -0,0 +1,6 @@
|
||||
class SoundsController < ApplicationController
|
||||
def show
|
||||
sound = Sound.find params[:id]
|
||||
send_file sound.path, :type => sound.mime_type
|
||||
end
|
||||
end
|
@ -8,19 +8,11 @@ class TracksController < ApplicationController
|
||||
end
|
||||
|
||||
def create
|
||||
@track = Track.new(:name => params[:track][:name])
|
||||
if @track.save_with_file(
|
||||
params[:track][:file],
|
||||
params[:track][:file].content_type
|
||||
)
|
||||
@track = Track.new params[:track]
|
||||
if @track.save
|
||||
redirect_to @track
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
def download
|
||||
track = Track.find params[:id]
|
||||
send_file track.filepath, :type => track.mime_type
|
||||
end
|
||||
end
|
||||
|
18
app/models/sound.rb
Normal file
18
app/models/sound.rb
Normal file
@ -0,0 +1,18 @@
|
||||
require 'fileutils'
|
||||
|
||||
class Sound < ActiveRecord::Base
|
||||
belongs_to :track
|
||||
|
||||
validates_presence_of :sha256
|
||||
validates_presence_of :mime_type
|
||||
|
||||
def path
|
||||
"#{Rails.root}/data/sounds/#{sha256}"
|
||||
end
|
||||
|
||||
def file=(file)
|
||||
self.sha256 = Digest::SHA256.file(file.path).hexdigest
|
||||
FileUtils.cp file.path, path
|
||||
self.mime_type = file.content_type
|
||||
end
|
||||
end
|
@ -1,19 +1,20 @@
|
||||
class Track < ActiveRecord::Base
|
||||
validates_presence_of :name
|
||||
validates_presence_of :mime_type
|
||||
validates_presence_of :sha256
|
||||
has_many :sounds
|
||||
|
||||
def filepath
|
||||
"#{Rails.root}/data/tracks/#{sha256}"
|
||||
attr_accessible :name, :file
|
||||
|
||||
validates_presence_of :name
|
||||
|
||||
def file=(file)
|
||||
sounds.build({:file => file})
|
||||
end
|
||||
|
||||
def save_with_file(file, mime_type)
|
||||
self.sha256 = Digest::SHA256.file(file.path).hexdigest
|
||||
self.mime_type = mime_type
|
||||
File.open(filepath, 'w') do |f|
|
||||
f.write file.read
|
||||
end
|
||||
save!
|
||||
def sound
|
||||
sounds.first
|
||||
end
|
||||
|
||||
def sound?
|
||||
sounds.any?
|
||||
end
|
||||
|
||||
def self.latest
|
||||
|
@ -1,3 +1,4 @@
|
||||
%h1= @track.name
|
||||
%audio{:src => download_track_path(@track), :controls => true, :autoplay => true}
|
||||
Your browser does not support the audio element
|
||||
- if @track.sound?
|
||||
%audio{:src => sound_path(@track.sound), :controls => true, :autoplay => true}
|
||||
Your browser does not support the audio element
|
||||
|
@ -1,4 +1,6 @@
|
||||
Scube::Application.routes.draw do
|
||||
resources :sounds, :only => [:show]
|
||||
|
||||
resources :users, :only => [:new, :create]
|
||||
|
||||
resources :sessions, :only => [:new, :create]
|
||||
|
@ -1,13 +0,0 @@
|
||||
class CreatePlaylists < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :playlists do |t|
|
||||
t.string :name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :playlists
|
||||
end
|
||||
end
|
@ -1,13 +0,0 @@
|
||||
class CreateTracks < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :tracks do |t|
|
||||
t.string :name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :tracks
|
||||
end
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class AddMimeTypeFieldToTracks < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :tracks, :mime_type, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :tracks, :mime_type
|
||||
end
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class AddSha256FieldToTracks < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :tracks, :sha256, :string
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :tracks, :sha256
|
||||
end
|
||||
end
|
@ -1,16 +0,0 @@
|
||||
class AddSessionsTable < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :sessions do |t|
|
||||
t.string :session_id, :null => false
|
||||
t.text :data
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :sessions, :session_id
|
||||
add_index :sessions, :updated_at
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :sessions
|
||||
end
|
||||
end
|
@ -1,14 +0,0 @@
|
||||
class CreateUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :users do |t|
|
||||
t.string :email
|
||||
t.string :password
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :users
|
||||
end
|
||||
end
|
@ -1,11 +0,0 @@
|
||||
class AddPasswordHashToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :users, :password_hash, :string
|
||||
remove_column :users, :password
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :users, :password_hash
|
||||
add_column :users, :password, :string
|
||||
end
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class AddUserIdToPlaylists < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_column :playlists, :user_id, :integer
|
||||
end
|
||||
|
||||
def self.down
|
||||
remove_column :playlists, :user_id
|
||||
end
|
||||
end
|
@ -1,9 +0,0 @@
|
||||
class AddEmailUniqueIndexToUsers < ActiveRecord::Migration
|
||||
def self.up
|
||||
add_index :users, :email, :unique => true
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_index :users, :email
|
||||
end
|
||||
end
|
48
db/migrate/20110915211845_initial_schema.rb
Normal file
48
db/migrate/20110915211845_initial_schema.rb
Normal file
@ -0,0 +1,48 @@
|
||||
class InitialSchema < ActiveRecord::Migration
|
||||
def up
|
||||
|
||||
create_table :playlists do |t|
|
||||
t.integer :user_id
|
||||
t.string :name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :sessions do |t|
|
||||
t.string :session_id, :null => false
|
||||
t.text :data
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :sessions, :session_id
|
||||
add_index :sessions, :updated_at
|
||||
|
||||
create_table :tracks do |t|
|
||||
t.string :name
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
create_table :users do |t|
|
||||
t.string :email
|
||||
t.string :password_hash
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
add_index :users, :email, :unique => true
|
||||
|
||||
end
|
||||
|
||||
def down
|
||||
drop_index :sessions, :session_id
|
||||
drop_index :sessions, :updated_at
|
||||
drop_index :users, :email
|
||||
|
||||
drop_table :playlists
|
||||
drop_table :sessions
|
||||
drop_table :tracks
|
||||
drop_table :users
|
||||
end
|
||||
end
|
15
db/migrate/20110916003929_create_sounds.rb
Normal file
15
db/migrate/20110916003929_create_sounds.rb
Normal file
@ -0,0 +1,15 @@
|
||||
class CreateSounds < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :sounds do |t|
|
||||
t.integer :track_id
|
||||
t.string :sha256
|
||||
t.string :mime_type
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :sounds
|
||||
end
|
||||
end
|
16
db/schema.rb
16
db/schema.rb
@ -10,13 +10,13 @@
|
||||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20110913195421) do
|
||||
ActiveRecord::Schema.define(:version => 20110916003929) do
|
||||
|
||||
create_table "playlists", :force => true do |t|
|
||||
t.integer "user_id"
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.integer "user_id"
|
||||
end
|
||||
|
||||
create_table "sessions", :force => true do |t|
|
||||
@ -29,19 +29,25 @@ ActiveRecord::Schema.define(:version => 20110913195421) do
|
||||
add_index "sessions", ["session_id"], :name => "index_sessions_on_session_id"
|
||||
add_index "sessions", ["updated_at"], :name => "index_sessions_on_updated_at"
|
||||
|
||||
create_table "sounds", :force => true do |t|
|
||||
t.integer "track_id"
|
||||
t.string "sha256"
|
||||
t.string "mime_type"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
|
||||
create_table "tracks", :force => true do |t|
|
||||
t.string "name"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "mime_type"
|
||||
t.string "sha256"
|
||||
end
|
||||
|
||||
create_table "users", :force => true do |t|
|
||||
t.string "email"
|
||||
t.string "password_hash"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "password_hash"
|
||||
end
|
||||
|
||||
add_index "users", ["email"], :name => "index_users_on_email", :unique => true
|
||||
|
25
spec/controllers/sounds_controller_spec.rb
Normal file
25
spec/controllers/sounds_controller_spec.rb
Normal file
@ -0,0 +1,25 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SoundsController do
|
||||
before do
|
||||
controller.current_user = Factory.create(:user)
|
||||
end
|
||||
|
||||
describe 'GET show' do
|
||||
let(:sound) { Factory.create(:sound) }
|
||||
|
||||
def do_show
|
||||
get :show, :id => sound.id
|
||||
end
|
||||
|
||||
it 'sends the sound file as the response body' do
|
||||
do_show
|
||||
response.body.should == File.read(sound.path)
|
||||
end
|
||||
|
||||
it 'sets the sound file mime-type as the response content-type' do
|
||||
do_show
|
||||
response.content_type.should == sound.mime_type
|
||||
end
|
||||
end
|
||||
end
|
@ -13,20 +13,6 @@ describe TracksController do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET download' do
|
||||
let(:track) { Factory.create(:track) }
|
||||
|
||||
it 'streams the requested track' do
|
||||
get :download, :id => track.id.to_s
|
||||
response.should be_success
|
||||
end
|
||||
|
||||
it 'returns the track mime-type as content-type' do
|
||||
get :download, :id => track.id.to_s
|
||||
response.content_type.should == track.mime_type
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET new' do
|
||||
it 'assigns a new track as @track' do
|
||||
get :new
|
||||
@ -35,48 +21,46 @@ describe TracksController do
|
||||
end
|
||||
|
||||
describe 'POST create' do
|
||||
let(:track) { mock_model(Track).as_null_object }
|
||||
let(:file) {
|
||||
fixture_file_upload("#{Rails.root}/spec/fixtures/test.mp3", 'audio/mpeg')
|
||||
}
|
||||
before { Track.stub(:new).and_return(track) }
|
||||
let(:track) { mock_model(Track).as_null_object }
|
||||
let(:attributes) { Factory.attributes_for(:track).stringify_keys }
|
||||
|
||||
it 'creates a new track' do
|
||||
attributes = Factory.attributes_for(:track)
|
||||
Track.should_receive(:new).
|
||||
with({:name => attributes[:name]}).
|
||||
and_return(track)
|
||||
post :create, :track => {
|
||||
:name => attributes[:name],
|
||||
:file => file
|
||||
}
|
||||
before do
|
||||
Track.stub(:new).and_return(track)
|
||||
end
|
||||
|
||||
it 'saves the track with a file' do
|
||||
track.should_receive(:save_with_file).
|
||||
with(file, 'audio/mpeg')
|
||||
post :create, :track => { :file => file }
|
||||
def do_create
|
||||
post :create, :track => attributes
|
||||
end
|
||||
|
||||
it 'creates a new track' do
|
||||
Track.should_receive(:new).with(attributes)
|
||||
do_create
|
||||
end
|
||||
|
||||
it 'saves the track' do
|
||||
track.should_receive :save
|
||||
do_create
|
||||
end
|
||||
|
||||
context 'when the track saves successfully' do
|
||||
it 'redirects to the track page' do
|
||||
post :create, :track => { :file => file }
|
||||
do_create
|
||||
response.should redirect_to(track)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the track fails to save' do
|
||||
before do
|
||||
track.stub(:save_with_file).and_return(false)
|
||||
track.stub(:save).and_return(false)
|
||||
end
|
||||
|
||||
it 'assigns the track as @track' do
|
||||
post :create, :track => { :file => file }
|
||||
do_create
|
||||
assigns[:track].should == track
|
||||
end
|
||||
|
||||
it 'renders the new template' do
|
||||
post :create, :track => { :file => file }
|
||||
do_create
|
||||
response.should render_template('new')
|
||||
end
|
||||
end
|
||||
|
@ -1,16 +1,24 @@
|
||||
def build_sound_file
|
||||
file = File.new("#{Rails.root}/spec/fixtures/test.mp3")
|
||||
file.stub(:content_type => 'audio/mpeg')
|
||||
file
|
||||
end
|
||||
|
||||
FactoryGirl.define do
|
||||
factory :playlist do
|
||||
name 'Electro'
|
||||
user
|
||||
end
|
||||
|
||||
factory :sound do
|
||||
file { build_sound_file }
|
||||
end
|
||||
|
||||
factory :track do
|
||||
name 'Mega song'
|
||||
mime_type 'audio/ogg'
|
||||
sha256 '94a5486a69a7261da350c57f9e5a1eaa789e08752cfc56a1989976a6ad82f7a8'
|
||||
|
||||
after_create do |t|
|
||||
t.save_with_file(File.new("#{Rails.root}/spec/fixtures/test.mp3"), 'audio/mpeg')
|
||||
factory :track_with_sound do
|
||||
file { build_sound_file }
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -28,17 +28,15 @@ feature 'Tracks' do
|
||||
end
|
||||
|
||||
scenario 'plays track' do
|
||||
track = Factory.create(:track, :name => 'Mega song')
|
||||
file = File.new("#{Rails.root}/spec/fixtures/test.mp3")
|
||||
track.save_with_file(file, 'audio/mpeg')
|
||||
track = Factory.create(:track_with_sound)
|
||||
|
||||
visit track_path(track)
|
||||
|
||||
page.should have_xpath "//audio[@src='#{download_track_path(track)}']"
|
||||
page.should have_xpath "//audio[@src='#{sound_path(track.sound)}']"
|
||||
visit find('audio')[:src]
|
||||
end
|
||||
|
||||
after do
|
||||
`rm -f #{Rails.root}/data/tracks/*`
|
||||
`rm -f #{Rails.root}/data/sounds/*`
|
||||
end
|
||||
end
|
||||
|
46
spec/models/sound_spec.rb
Normal file
46
spec/models/sound_spec.rb
Normal file
@ -0,0 +1,46 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Sound do
|
||||
subject { sound }
|
||||
let(:sound) { Factory.build(:sound) }
|
||||
|
||||
context 'with valid attributes' do
|
||||
it { should be_valid }
|
||||
end
|
||||
|
||||
context 'when sha256 empty' do
|
||||
before { sound.sha256 = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
context 'when mime_type empty' do
|
||||
before { sound.mime_type = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
describe '#path' do
|
||||
it 'returns the sound file path based on the SHA256 digest' do
|
||||
sound.path.should == "#{Rails.root}/data/sounds/#{sound.sha256}"
|
||||
end
|
||||
end
|
||||
|
||||
describe '#file=' do
|
||||
let (:file) { Factory.attributes_for(:sound)[:file] }
|
||||
|
||||
it 'saves the file SHA256 digest' do
|
||||
sound.sha256.should == Digest::SHA256.file(file.path).hexdigest
|
||||
end
|
||||
|
||||
it 'copies the file to #path' do
|
||||
File.read(sound.path).should == file.read
|
||||
end
|
||||
|
||||
it 'saves the file MIME type' do
|
||||
sound.mime_type.should == 'audio/mpeg'
|
||||
end
|
||||
end
|
||||
|
||||
after do
|
||||
`rm -f #{Rails.root}/data/sounds/*`
|
||||
end
|
||||
end
|
@ -1,7 +1,7 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Track do
|
||||
subject { track }
|
||||
subject { track }
|
||||
let(:track) { Factory.build(:track) }
|
||||
|
||||
context 'with valid attributes' do
|
||||
@ -13,47 +13,49 @@ describe Track do
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
context 'when mime_type empty' do
|
||||
before { track.mime_type = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
context 'when sha256 empty' do
|
||||
before { track.sha256 = '' }
|
||||
it { should_not be_valid }
|
||||
end
|
||||
|
||||
describe '#filepath' do
|
||||
it 'returns the path to the track file' do
|
||||
track.filepath.should == "#{Rails.root}/data/tracks/#{track.sha256}"
|
||||
describe '#file=' do
|
||||
it 'builds a new related sound with the file' do
|
||||
file = Factory.attributes_for(:track_with_sound)[:file]
|
||||
sounds = mock('sounds association proxy')
|
||||
track.stub(:sounds => sounds)
|
||||
sounds.should_receive(:build).with({:file => file})
|
||||
track.file = file
|
||||
end
|
||||
end
|
||||
|
||||
describe '#save_with_file' do
|
||||
let(:file) { File.new("#{Rails.root}/spec/fixtures/test.mp3") }
|
||||
describe '#sounds' do
|
||||
it 'responds to sound' do
|
||||
track.should respond_to(:sounds)
|
||||
end
|
||||
end
|
||||
|
||||
it 'calls save' do
|
||||
Track.any_instance.should_receive(:save!)
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
describe '#sound' do
|
||||
context 'with a sound' do
|
||||
before do
|
||||
track.sounds << Factory.create(:sound)
|
||||
end
|
||||
|
||||
it 'returns a sound' do
|
||||
track.sound.should be_a(Sound)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sound?' do
|
||||
context 'without a sound' do
|
||||
it 'returns false' do
|
||||
track.sound?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it 'saves the file content' do
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
File.read(track.filepath).should == File.read(file.path)
|
||||
end
|
||||
context 'with a sound' do
|
||||
before do
|
||||
track.sounds << Factory.create(:sound)
|
||||
end
|
||||
|
||||
it 'saves the file SHA256 digest' do
|
||||
track.save_with_file(file, 'audio/ogg')
|
||||
track.sha256.should == Digest::SHA256.file(file.path).hexdigest
|
||||
end
|
||||
|
||||
it 'saves the file mime type' do
|
||||
track.save_with_file(file, 'audio/mpeg')
|
||||
track.mime_type.should == 'audio/mpeg'
|
||||
end
|
||||
|
||||
after do
|
||||
`rm -f #{Rails.root}/data/tracks/*`
|
||||
it 'returns true' do
|
||||
track.sound?.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1,9 +1,7 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'tracks/show.html.haml' do
|
||||
let(:track) do
|
||||
mock_model('Track', :name => 'Mega song')
|
||||
end
|
||||
let(:track) { Factory.create(:track) }
|
||||
|
||||
before do
|
||||
assign :track, track
|
||||
@ -14,7 +12,9 @@ describe 'tracks/show.html.haml' do
|
||||
rendered.should have_selector('h1', :text => 'Mega song')
|
||||
end
|
||||
|
||||
context 'audio tag' do
|
||||
context 'when track has a sound' do
|
||||
let(:track) { Factory.create(:track_with_sound) }
|
||||
|
||||
it 'provides an audio stream for the track' do
|
||||
render
|
||||
rendered.should have_selector('audio[src]')
|
||||
|
Loading…
x
Reference in New Issue
Block a user