From 27550fd14e64205824be0e68dff5e39b8dd557c7 Mon Sep 17 00:00:00 2001 From: Thibault Jouan Date: Thu, 15 Sep 2011 21:08:24 +0000 Subject: [PATCH] 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. --- app/controllers/sounds_controller.rb | 6 ++ app/controllers/tracks_controller.rb | 12 +--- app/models/sound.rb | 18 +++++ app/models/track.rb | 25 +++---- app/views/tracks/show.html.haml | 5 +- config/routes.rb | 2 + data/{tracks => sounds}/.gitkeep | 0 db/migrate/20110711195337_create_playlists.rb | 13 ---- db/migrate/20110713182005_create_tracks.rb | 13 ---- ...725110405_add_mime_type_field_to_tracks.rb | 9 --- ...110726101228_add_sha256_field_to_tracks.rb | 9 --- .../20110804225816_add_sessions_table.rb | 16 ----- db/migrate/20110805201426_create_users.rb | 14 ---- ...110809130610_add_password_hash_to_users.rb | 11 --- ...20110830110346_add_user_id_to_playlists.rb | 9 --- ...3195421_add_email_unique_index_to_users.rb | 9 --- db/migrate/20110915211845_initial_schema.rb | 48 +++++++++++++ db/migrate/20110916003929_create_sounds.rb | 15 ++++ db/schema.rb | 16 +++-- spec/controllers/sounds_controller_spec.rb | 25 +++++++ spec/controllers/tracks_controller_spec.rb | 56 ++++++--------- spec/factories.rb | 16 +++-- spec/integration/tracks_spec.rb | 8 +-- spec/models/sound_spec.rb | 46 ++++++++++++ spec/models/track_spec.rb | 72 ++++++++++--------- spec/views/tracks/show.html.haml_spec.rb | 8 +-- 26 files changed, 265 insertions(+), 216 deletions(-) create mode 100644 app/controllers/sounds_controller.rb create mode 100644 app/models/sound.rb rename data/{tracks => sounds}/.gitkeep (100%) delete mode 100644 db/migrate/20110711195337_create_playlists.rb delete mode 100644 db/migrate/20110713182005_create_tracks.rb delete mode 100644 db/migrate/20110725110405_add_mime_type_field_to_tracks.rb delete mode 100644 db/migrate/20110726101228_add_sha256_field_to_tracks.rb delete mode 100644 db/migrate/20110804225816_add_sessions_table.rb delete mode 100644 db/migrate/20110805201426_create_users.rb delete mode 100644 db/migrate/20110809130610_add_password_hash_to_users.rb delete mode 100644 db/migrate/20110830110346_add_user_id_to_playlists.rb delete mode 100644 db/migrate/20110913195421_add_email_unique_index_to_users.rb create mode 100644 db/migrate/20110915211845_initial_schema.rb create mode 100644 db/migrate/20110916003929_create_sounds.rb create mode 100644 spec/controllers/sounds_controller_spec.rb create mode 100644 spec/models/sound_spec.rb diff --git a/app/controllers/sounds_controller.rb b/app/controllers/sounds_controller.rb new file mode 100644 index 0000000..a88a193 --- /dev/null +++ b/app/controllers/sounds_controller.rb @@ -0,0 +1,6 @@ +class SoundsController < ApplicationController + def show + sound = Sound.find params[:id] + send_file sound.path, :type => sound.mime_type + end +end diff --git a/app/controllers/tracks_controller.rb b/app/controllers/tracks_controller.rb index a3d0cd8..00c1537 100644 --- a/app/controllers/tracks_controller.rb +++ b/app/controllers/tracks_controller.rb @@ -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 diff --git a/app/models/sound.rb b/app/models/sound.rb new file mode 100644 index 0000000..8fe4d3c --- /dev/null +++ b/app/models/sound.rb @@ -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 diff --git a/app/models/track.rb b/app/models/track.rb index cce1e7f..745c791 100644 --- a/app/models/track.rb +++ b/app/models/track.rb @@ -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 diff --git a/app/views/tracks/show.html.haml b/app/views/tracks/show.html.haml index e404960..a8683c8 100644 --- a/app/views/tracks/show.html.haml +++ b/app/views/tracks/show.html.haml @@ -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 diff --git a/config/routes.rb b/config/routes.rb index 0dbfabb..4ec0381 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,6 @@ Scube::Application.routes.draw do + resources :sounds, :only => [:show] + resources :users, :only => [:new, :create] resources :sessions, :only => [:new, :create] diff --git a/data/tracks/.gitkeep b/data/sounds/.gitkeep similarity index 100% rename from data/tracks/.gitkeep rename to data/sounds/.gitkeep diff --git a/db/migrate/20110711195337_create_playlists.rb b/db/migrate/20110711195337_create_playlists.rb deleted file mode 100644 index 5e94ab0..0000000 --- a/db/migrate/20110711195337_create_playlists.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110713182005_create_tracks.rb b/db/migrate/20110713182005_create_tracks.rb deleted file mode 100644 index 8573c79..0000000 --- a/db/migrate/20110713182005_create_tracks.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110725110405_add_mime_type_field_to_tracks.rb b/db/migrate/20110725110405_add_mime_type_field_to_tracks.rb deleted file mode 100644 index d49ef93..0000000 --- a/db/migrate/20110725110405_add_mime_type_field_to_tracks.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110726101228_add_sha256_field_to_tracks.rb b/db/migrate/20110726101228_add_sha256_field_to_tracks.rb deleted file mode 100644 index f181212..0000000 --- a/db/migrate/20110726101228_add_sha256_field_to_tracks.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110804225816_add_sessions_table.rb b/db/migrate/20110804225816_add_sessions_table.rb deleted file mode 100644 index 8e644d2..0000000 --- a/db/migrate/20110804225816_add_sessions_table.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110805201426_create_users.rb b/db/migrate/20110805201426_create_users.rb deleted file mode 100644 index 8552060..0000000 --- a/db/migrate/20110805201426_create_users.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110809130610_add_password_hash_to_users.rb b/db/migrate/20110809130610_add_password_hash_to_users.rb deleted file mode 100644 index b38ed33..0000000 --- a/db/migrate/20110809130610_add_password_hash_to_users.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110830110346_add_user_id_to_playlists.rb b/db/migrate/20110830110346_add_user_id_to_playlists.rb deleted file mode 100644 index a487f08..0000000 --- a/db/migrate/20110830110346_add_user_id_to_playlists.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110913195421_add_email_unique_index_to_users.rb b/db/migrate/20110913195421_add_email_unique_index_to_users.rb deleted file mode 100644 index fecae50..0000000 --- a/db/migrate/20110913195421_add_email_unique_index_to_users.rb +++ /dev/null @@ -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 diff --git a/db/migrate/20110915211845_initial_schema.rb b/db/migrate/20110915211845_initial_schema.rb new file mode 100644 index 0000000..af32c53 --- /dev/null +++ b/db/migrate/20110915211845_initial_schema.rb @@ -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 diff --git a/db/migrate/20110916003929_create_sounds.rb b/db/migrate/20110916003929_create_sounds.rb new file mode 100644 index 0000000..aaf549f --- /dev/null +++ b/db/migrate/20110916003929_create_sounds.rb @@ -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 diff --git a/db/schema.rb b/db/schema.rb index 63357fb..a59623d 100644 --- a/db/schema.rb +++ b/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 diff --git a/spec/controllers/sounds_controller_spec.rb b/spec/controllers/sounds_controller_spec.rb new file mode 100644 index 0000000..aea2611 --- /dev/null +++ b/spec/controllers/sounds_controller_spec.rb @@ -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 diff --git a/spec/controllers/tracks_controller_spec.rb b/spec/controllers/tracks_controller_spec.rb index 4cf6727..8196193 100644 --- a/spec/controllers/tracks_controller_spec.rb +++ b/spec/controllers/tracks_controller_spec.rb @@ -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 diff --git a/spec/factories.rb b/spec/factories.rb index 1cb8dab..dd35f49 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -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 diff --git a/spec/integration/tracks_spec.rb b/spec/integration/tracks_spec.rb index 3151493..f80e2f9 100644 --- a/spec/integration/tracks_spec.rb +++ b/spec/integration/tracks_spec.rb @@ -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 diff --git a/spec/models/sound_spec.rb b/spec/models/sound_spec.rb new file mode 100644 index 0000000..8c5204f --- /dev/null +++ b/spec/models/sound_spec.rb @@ -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 diff --git a/spec/models/track_spec.rb b/spec/models/track_spec.rb index 02e6eb7..cfcb169 100644 --- a/spec/models/track_spec.rb +++ b/spec/models/track_spec.rb @@ -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 diff --git a/spec/views/tracks/show.html.haml_spec.rb b/spec/views/tracks/show.html.haml_spec.rb index f30097e..3a7eb0c 100644 --- a/spec/views/tracks/show.html.haml_spec.rb +++ b/spec/views/tracks/show.html.haml_spec.rb @@ -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]')