В этом руководстве Tuts + Premium мы узнаем, как создать веб-приложение для обмена файлами, например Dropbox , с использованием Ruby on Rails.
Вступление
Огромный успех DropBox ясно показывает, что существует огромная потребность в простом и быстром обмене файлами.
Наше приложение будет иметь следующие функции:
- простая аутентификация пользователя
- загружать файлы и сохранять их в Amazon S3
- создавать папки и организовывать
- делиться папками с другими пользователями
В этом уроке я укажу различные способы улучшения нашего приложения. Попутно мы рассмотрим различные концепции, в том числе Rails и AJAX.
Шаг 0 Готовимся
«Убедитесь, что вы обновились до Rails 3, чтобы следовать этому уроку».
Прежде чем начать, убедитесь, что на вашем компьютере установлены все следующие компоненты:
- Ruby 1.9. +
- Рельсы 3+
- MySQL 5
Если вы новичок в Ruby и Rails, вы можете начать процесс обучения здесь . Мы будем использовать терминал (Mac) или командную строку, если вы пользователь Windows.
Шаг 1 Создайте новое приложение Rails
Во-первых, нам нужно создать приложение Rails. Откройте терминал и перейдите к нужной папке. Мы назовем наше приложение «ShareBox». Введите следующее в Терминал.
1
|
Rails new sharebox -d mysql
|
«Опция
-d mysql
добавлена, чтобы приложение использовало MySQL в качестве своей базы данных, поскольку по умолчанию оно будет использовать sqlite3».
Это создаст структуру файлов и папок, подобную той, что показана ниже.
Теперь мы должны убедиться, что у нас установлены правильные камни. Откройте «Gemfile» из вашего каталога Rails и замените весь код внутри следующим:
1
2
3
4
|
source ‘http://rubygems.org’
gem ‘Rails’, ‘3.0.3’
gem ‘ruby-mysql’
|
«Gemfile — это файл, в который вы помещаете все необходимые гемы, необходимые для запуска этого конкретного приложения. Он помогает вам организовать гемы, которые вам понадобятся».
Поскольку мы используем Rails 3 и MySQL, эти два гема добавляются в Gemfile. Затем нам нужно запустить следующую команду в каталоге Rails «sharebox», используя Terminal. Если вы не находитесь в каталоге «sharebox», введите «cd sharebox», чтобы перейти в папку.
1
|
bundle install
|
Команда bundle install
установит все гемы, определенные в Gemfile, если они еще не были установлены.
Далее, давайте удостоверимся, что у нас есть работающее соединение с базой данных, и база данных, «sharebox», настроена. В папке «sharebox» откройте «config / database.yml». Измените настройки среды разработки и тестирования следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
development:
adapter: mysql
encoding: utf8
reconnect: false
database: sharebox_development
pool: 5
username: root
password:
socket: /tmp/mysql.sock
test:
adapter: mysql
encoding: utf8
reconnect: false
database: sharebox_test
pool: 5
username: root
password:
socket: /tmp/mysql.sock
|
Конечно, замените ваши собственные детали подключения MySQL, соответственно. Кроме того, вам может понадобиться изменить сокет и добавить host
чтобы он работал. Выполните следующую команду в Терминале (в папке «sharebox»).
1
|
rake db:create
|
«Если вы не видите никаких отзывов после выполнения этой команды, вы можете идти».
Теперь, если вы это сделаете, вам, вероятно, потребуется изменить файл database.yml, чтобы он соответствовал настройкам соединения MySQL вашей системы.
Давайте рассмотрим наше приложение в браузере. Запустите следующее в Терминале:
1
|
Rails server
|
Вы увидите что-то вроде следующего в вашем терминале.
Теперь запустите ваш любимый браузер, такой как Firefox или Chrome, и введите его в адресную строку:
1
|
http://localhost:3000/
|
Вы должны увидеть домашнюю страницу Rails по умолчанию следующим образом:
Шаг 2 Создайте аутентификацию пользователя
Далее мы создадим базовую аутентификацию пользователя.
Мы будем использовать фантастический камень разработки, чтобы помочь с нашей аутентификацией пользователя. Мы также можем использовать отличный генератор Райана, чтобы помочь с нашим макетом и просмотреть помощников.
Добавьте эти два камня в свой Gemfile внизу:
1
2
3
4
5
|
#for user authentication
gem ‘devise’
#for layout and helpers generations
gem «nifty-generators», :group => :development
|
«Не забудьте запустить
bundle install
чтобы установить эти драгоценные камни в вашей системе».
После установки гемов давайте начнем с установки некоторых макетов с помощью nifty-generator. Введите следующее в Терминал, чтобы сгенерировать файлы макета в каталоге «sharebox».
1
|
Rails g nifty:layout
|
Msgstr «‘Rails g’ является сокращением для
Rails generate.
Он спросит вас, хотите ли вы перезаписать «layouts / application.html.erb». Нажмите «Y» и введите клавишу для продолжения. Затем он создаст несколько файлов, которые мы будем использовать через некоторое время.
Теперь давайте установим «devise». Введите это в Терминале:
1
|
Rails g devise:install
|
Эта команда установит несколько файлов, но также попросит вас выполнить три вещи вручную.
Сначала скопируйте следующую строку и вставьте ее в «config / environment / development.rb». Вставьте его перед последним «концом».
1
|
config.action_mailer.default_url_options = { :host => ‘localhost:3000’ }
|
Во-вторых, нам нужно настроить root_url в файле «config / rout.rb». Откройте файл и добавьте:
1
2
3
4
|
Sharebox::Application.routes.draw do
root :to => «home#index»
end
|
Мы пропустим последний шаг, поскольку он был сделан изящным генератором.
Теперь давайте создадим нашу первую модель User
, используя «devise».
1
2
3
4
|
Sharebox::Application.routes.draw do
root :to => «home#index»
end
|
Это создаст несколько файлов в вашем каталоге Rails. Давайте кратко рассмотрим файл миграции «db / migrate / [datetime] _devise_create_users.rb». Это должно выглядеть примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
# t.confirmable
# t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
# t.token_authenticatable
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
# add_index :users, :confirmation_token, :unique => true
# add_index :users, :unlock_token, :unique => true
end
def self.down
drop_table :users
end
end
|
Devise содержит встроенные функции, готовые к использованию из коробки. Среди этих замечательных функций мы будем использовать:
-
database_authenticatable
: аутентифицирует пользователей. -
recoverable
: позволяет пользователю восстановить или изменить пароль, если это необходимо. -
rememberable
: позволяет пользователю «запоминать» свою учетную запись в своей системе при каждом входе в систему. -
trackable
:trackable
в систему, метки времени и IP-адрес.
Миграция также добавляет два индекса для «email» и «reset_password_token».
Мы не сильно изменимся, но добавим поле «имя» в таблицу и удалим все закомментированные строки следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
class DeviseCreateUsers < ActiveRecord::Migration
def self.up
create_table(:users) do |t|
t.database_authenticatable :null => false
t.recoverable
t.rememberable
t.trackable
t.string :name
t.timestamps
end
add_index :users, :email, :unique => true
add_index :users, :reset_password_token, :unique => true
end
def self.down
drop_table :users
end
end
|
Далее запустите rake db:migrate
в Терминале. Это должно создать таблицу «пользователи» в базе данных. Давайте проверим модель пользователя в «app / models / user.rb» и добавим туда атрибут «name». Кроме того, давайте добавим немного проверки.
01
02
03
04
05
06
07
08
09
10
11
|
class User < ActiveRecord::Base
# Include default devise modules.
# :token_authenticatable, :confirmable, :lockable and :timeoutable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :name, :password, :password_confirmation, :remember_me
validates :email, :presence => true, :uniqueness => true
end
|
Прежде чем двигаться дальше, нам нужно быстро добавить контроллер Home с действием Index для корневого URL. Создайте файл «home_controller.rb» в папке «app / controllers». Затем добавьте следующее в файл для действия Index:
1
2
3
4
5
6
|
class HomeController < ApplicationController
def index
end
end
|
Не забывайте; мы должны создать представление для этого также. Создайте папку с именем «home» внутри «app / views /» и добавьте туда файл «index.html.erb». Добавьте следующее к этому файлу.
1
|
This is the Index.html.erb under «app/views/home» folder.
|
«Нам нужно удалить / удалить файл public / index.html, чтобы root_url работал правильно».
Теперь давайте попробуем создать пользователя, а затем войти / выйти, чтобы почувствовать магию устройства. Вам нужно перезапустить сервер Rails (нажав Ctrl + C и запустить ‘Rails server’), чтобы перезагрузить все новые установленные файлы из устройства. Затем перейдите на http: // localhost: 3000, и вы должны увидеть:
Если мы перейдем по http://localhost:3000/users/sign_up
, который является одним из маршрутов по умолчанию, уже встроенных в Devise, для регистрации пользователей вы должны увидеть это:
Обратите внимание, что поле name
еще не там? Нам нужно изменить вид, чтобы добавить имя. Поскольку представления Devise по умолчанию скрыты, мы должны отобразить их с помощью следующей команды.
1
|
Rails g devise:views
|
Откройте «app / views / devise / registrations / new.html.erb» и отредактируйте файл, добавив поле «name» в форму.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<h2>Sign up</h2>
<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name)) do |f|
<%= devise_error_messages!
<p><%= f.label :email %><br />
<%= f.text_field :email %></p>
<p><%= f.label :name %><br />
<%= f.text_field :name %></p>
<p><%= f.label :password %><br />
<%= f.password_field :password %></p>
<p><%= f.label :password_confirmation %><br />
<%= f.password_field :password_confirmation %></p>
<p><%= f.submit «Sign up» %></p>
<% end %>
<%= render :partial => «devise/shared/links» %>
|
Если вы обновите страницу, вы должны увидеть:
Если вы заполните форму и отправите ее для создания нового пользователя, вы увидите следующее изображение, которое означает, что вы успешно создали пользователя и вошли в систему.
Чтобы выйти, перейдите по адресу «http: // localhost: 3000 / users / sign_out», и вы выйдете из этого флеш-сообщения.
Затем вы можете войти в систему по адресу «http: // localhost: 3000 / users / sign_in», указав свои учетные данные:
Теперь у нас есть базовая аутентификация. Следующим шагом будет приведение в порядок макета и внешнего вида страниц вместе со ссылками.
Шаг 3 Добавьте базовый CSS
«Файл макета app / views / layouts / application.html.erb будет применен ко всем страницам просмотра, если вы не укажете свой собственный файл макета».
Прежде чем что-то делать, измените заголовок каждой страницы. Откройте файл «app / views / layouts / application.html.erb» и измените заголовок следующим образом:
1
|
<title>ShareBox |
|
Это сделает заголовки похожими на «ShareBox | Веб-приложение для обмена файлами», «ShareBox | Загрузить файлы» и так далее.
Далее мы добавим ссылки «Вход» и «Выход» в один и тот же файл макета application.html.erb. В теге body, непосредственно перед div
«container», добавьте следующее:
1
2
3
4
5
|
<div class=»header_wrapper»>
<div class=»logo»>
<%= link_to «ShareBox», root_url %>
</div>
</div>
|
Эта разметка / код добавит логотип (текст) с кликабельной ссылкой. Мы добавим немного CSS. Теперь поместите этот фрагмент HTML-кода с кодом Ruby для отображения имени пользователя, ссылки для выхода из системы. Убедитесь, что вы добавили это в div
«header_wrapper» после div
«logo».
01
02
03
04
05
06
07
08
09
10
11
12
|
<div id=»login_user_status»>
<% if user_signed_in?
<%= current_user.email %>
|
<%= link_to «Sign out», destroy_user_session_path %>
<% else %>
<em>Not Signed in.</em>
<%= link_to ‘Sign in’, new_user_session_path%>
or
<%= link_to ‘Sign up’, new_user_registration_path%>
<% end %>
</div>
|
«Метод
user_signed_in?
Определяет, вошел ли пользователь в систему».
Пути destroy_user_session_path
, new_user_session_path
и new_user_registration_path
являются путями по умолчанию, предоставляемыми устройством для выхода из системы, входа в систему и регистрации соответственно.
Теперь давайте добавим необходимый CSS, чтобы все выглядело немного лучше. Откройте файл «public / stylesheets / application.css» и вставьте следующий CSS. Убедитесь, что вы заменили стили «body» и «container».
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
body {
background-color: #EFEFEF;
font-family: «Lucida Grande»,»Verdana»,»Arial»,»Bitstream Vera Sans»,sans-serif;
font-size: 14px;
}
.header_wrapper {
width: 880px;
margin: 0 auto;
overflow:hidden;
padding:20px 0;
}
.logo a{
color: #338DCF;
float: left;
font-size: 48px;
font-weight: bold;
text-shadow: 2px 2px 2px #FFFFFF;
text-decoration:none;
}
#container {
width: 800px;
margin: 0 auto;
background-color: #FFF;
padding: 20px 40px;
border: solid 1px #BFBFBF;
}
#login_user_status {
float:right;
}
|
Если вы обновите домашнюю страницу в своем браузере, вы увидите:
Шаг 4 Загрузка файлов
Мы будем использовать драгоценный камень Paperclip, чтобы помочь нам загружать файлы. Добавьте этот гем в Gemfile следующим образом и запустите «bundle install» в Терминале.
1
2
|
#for uploading files
gem «paperclip», «~> 2.3»
|
После установки мы создадим нашу таблицу файлов. Однако, поскольку слово «Файл» является одним из зарезервированных слов , мы просто будем использовать термин «Актив» для файлов.
Один пользователь может загрузить несколько файлов, поэтому нам нужно добавить user_id
в модель Asset
. Так что запустите это в Терминале, чтобы Asset
модель Asset
.
1
|
Rails g nifty:scaffold Asset user_id:integer
|
Затем перейдите во вновь созданный файл миграции в папке «db / migrate /» и добавьте индекс для user_id.
1
|
add_index :assets, :user_id
|
Запустите rake db:migrate
в Терминале, чтобы создать таблицу активов.
Затем нам нужно добавить отношения как в таблицу User
и в таблицу Asset
. В файле «app / models / user.rb» добавьте это.
1
|
has_many :assets
|
В «app / models / asset.rb» добавьте это:
1
|
belongs_to :user
|
Теперь пришло время использовать эти отношения в контроллере для загрузки соответствующих ресурсов (ресурсов) для каждого вошедшего в систему пользователя. В «app / controllers / assets_controller.rb» измените код следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
class AssetsController < ApplicationController
before_filter :authenticate_user!
def index
@assets = current_user.assets
end
def show
@asset = current_user.assets.find(params[:id])
end
def new
@asset = current_user.assets.new
end
def create
@asset = current_user.assets.new(params[:asset])
…
end
def edit
@asset = current_user.assets.find(params[:id])
end
def update
@asset = current_user.assets.find(params[:id])
…
end
def destroy
@asset = current_user.assets.find(params[:id])
…
end
end
|
В приведенном выше коде мы удостоверяемся, что запрошенный актив (-ы) фактически принадлежит или создан current_user (вошедшим в систему пользователем), поскольку «current_user.assets» даст вам активы, которые «принадлежат » Пользователь.
Затем запустим скрипт миграции Paperclip, чтобы добавить некоторые необходимые поля в модель активов. Мы назовем поле основного файла «uploaded_file». Так что введите это в Терминале.
1
|
Rails g paperclip asset uploaded_file
|
Запустите «rake db: migrate», чтобы добавить поля.
Теперь, когда мы добавили поле «uploaded_file» в таблицу, нам нужно добавить его в модель. Поэтому в модели «app / models / user.rb» добавьте поле «uploaded_file» и определите его как «вложение» с помощью Paperclip.
01
02
03
04
05
06
07
08
09
10
|
attr_accessible :user_id, :uploaded_file
belongs_to :user
#set up «uploaded_file» field as attached_file (using Paperclip)
has_attached_file :uploaded_file
validates_attachment_size :uploaded_file, :less_than => 10.megabytes
validates_attachment_presence :uploaded_file
|
«
validates_attachment_size
иvalidates_attachment_presence
— это методы проверки, предоставляемые Paperclip, чтобы позволить вам проверить загруженный файл. В этом случае мы проверяем, загружен ли файл и имеет ли размер файла менее 10 МБ».
Мы используем has_attached_file
чтобы определить, какое поле предназначено для сохранения данных загруженного файла. В любом случае, это предусмотрено Paperclip.
Прежде чем мы протестируем это, давайте немного изменим представление для загрузки файла. Итак, откройте файл «app / views / assets / _form.html.erb» и вставьте следующее.
1
2
3
4
5
6
7
8
|
<%= form_for @asset, :html => {:multipart => true} do |f|
<%= f.error_messages %>
<p>
<%= f.label :uploaded_file, «File» %><br />
<%= f.file_field :uploaded_file %>
</p>
<p><%= f.submit «Upload» %></p>
<% end %>
|
В методе form_for
мы добавили html-атрибут «multipart», чтобы он был true
. Всегда необходимо правильно разместить файл на сервере. Мы также удалили поле user_id
из представления.
Теперь пришло время проверить вещи.
«Перезапуск сервера Rails необходим при каждом добавлении и установке нового гема».
Если вы посетите «http: // localhost: 3000 / assets / new», вы должны увидеть что-то вроде:
Как только вы загрузите файл и перейдете по адресу «http: // localhost: 3000 / assets», вы увидите:
Это не совсем правильно, не так ли? Итак, давайте изменим представление списка активов. Откройте файл «app / views / assets / index.html.erb» и вставьте:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
<% title «Assets» %>
<table>
<tr>
<th>Uploaded Files</th>
</tr>
<% for asset in @assets %>
<tr>
<td><%= link_to asset.uploaded_file_file_name, asset.uploaded_file.url %></td>
<td><%= link_to «Show», asset %></td>
<td><%= link_to «Edit», edit_asset_path(asset) %></td>
<td><%= link_to «Destroy», asset, :confirm => ‘Are you sure?’, :method => :delete %></td>
</tr>
<% end %>
</table>
<p><%= link_to «New Asset», new_asset_path %></p>
|
Как видите, мы обновили заголовок «Загруженные файлы» и добавили ссылку вместо идентификатора пользователя. asset
теперь есть аксессор uploaded_file
для загрузки сведений о файле. Одним из доступных методов является «url», который дает вам полный URL-адрес расположения файла. Paperclip также сохраняет имя файла в названии поля, которое мы определили (в нашем случае это «uploaded_file») плюс «_file_name». Таким образом, у нас есть «uploaded_file_file_name» в качестве имени файла.
Имея это в виду, давайте быстро проведем рефакторинг, чтобы получить более подходящее имя для фактического имени файла. Поместите этот метод в модель актива, расположенную в «app / models / asset.rb».
1
2
3
|
def file_name
uploaded_file_file_name
end
|
Этот код должен позволять вам использовать что-то вроде «asset.file_name», чтобы получить действительное имя загруженного файла. Убедитесь, что вы обновили страницу index.html.erb с помощью нового метода file_name, как показано ниже.
1
|
<td><%= link_to asset.uploaded_file_file_name, asset.uploaded_file.url %></td>
|
Теперь, если мы обновим страницу «http: // localhost: 3000 / assets», вы увидите:
Если затем щелкнуть ссылку в файле, вы попадете на URL, например, «http: // localhost: 3000 / system / uploaded_files / 1 / original / Venus.gif? 1295900873».
«По умолчанию Paperclip будет сохранять файлы в корневом каталоге Rails в
system/[name of the file field]s/:id/original/:file_name
.»
Но эти ссылки общедоступны!
Одна из наших целей для этого приложения — защитить загруженные файлы от несанкционированного доступа. Но, как видите, ссылка открыта для общественности, и вы можете скачать ее даже после выхода из системы. Давайте это исправим.
Во-первых, нам следует подумать о хорошем URL-адресе для файла, который нужно загрузить. «system / uploaded_files / 1 / original /» выглядит красиво, верно?
«Таким образом, мы сделаем URL-адрес следующим образом:« http: // localhost: 3000 / assets / get / 1 », который выглядит намного лучше».
Давайте изменим модель Asset
чтобы сохранить загруженный файл в папке «assets», а не в «system». Измените код следующим образом:
1
2
3
|
has_attached_file :uploaded_file,
:url => «/assets/get/:id»,
:path => «:Rails_root/assets/:id/:basename.:extension»
|
Параметр :url
предназначен для URL-адреса, который вы увидите в адресной строке для загрузки файла, тогда как path
указывает место для хранения файла на вашем компьютере.
:Rails_root
— это физический корневой каталог вашего приложения Rails, такой как «C: \ web_apps \ sharebox» (windows) или «/ Users / phyowaiwin / Sites / apps / sharebox» (mac). Идентификатор :id
является идентификатором записи файла актива. :basename
— это имя файла (без расширения), а :extension
— для расширения файла.
По сути, мы инструктируем Paperclip хранить загруженные файлы в папке «assets», в корневой папке Rails. URL-адрес будет таким же, как если бы мы хотели: «http: // localhost: 3000 / assets / get / 1».
Теперь давайте уничтожим файл по адресу http: // localhost: 3000 / assets и повторно загрузим файл на http: // localhost: 3000 / assets / new .
Когда вы нажимаете на ссылку на файл, вы должны перейти к URL- адресу, например http: // localhost: 3000 / assets / get / 2, где должна отображаться эта ошибка.
Это связано с тем, что мы не добавили никаких маршрутов для обработки этого. Во-вторых, нет действия или контроллера, который бы позаботился о загрузке файла. Давайте сначала создадим маршрут в config/routes.rb
1
2
|
#this route is for file downloads
match «assets/get/:id» => «assets#get», :as => «download»
|
Это означает, что при обращении к URL-адресу он должен направляться к действию get
в assets_controller
. Это также отказывается от download_url()
named-route. Давайте быстро воспользуемся этим маршрутом в app/views/assets/index.html.erb
:
1
|
<%= link_to asset.file_name, download_url(asset) %>
|
Хорошо, теперь нам нужно добавить действие get
в app/controllers/assets_controller.rb
.
1
2
3
4
5
6
7
|
#this action will let the users download the files (after a simple authorization check)
def get
asset = current_user.assets.find_by_id(params[:id])
if asset
send_file asset.uploaded_file.path, :type => asset.uploaded_file_content_type
end
end
|
find_by_id(:id)
»find_by_id(:id)
вернетfind_by_id(:id)
если не найдена запись сid
, тогда какfind(:id)
вызовет исключение, если запись не найдена.»
Выше мы сначала берем id
и находим его в собственных активах current_user. Затем мы определяем, найден ли актив. Если это так, мы используем метод Rails send_file
чтобы позволить пользователю загрузить файл. send_file
принимает «путь» в качестве первого параметра и file_type (content_type) в качестве второго.
Теперь, если вы нажмете ссылку на файл на странице, вы сможете загрузить файл.
Теперь это намного лучше. Ссылка для скачивания теперь довольно безопасна. Теперь вы можете скопировать URL-адрес ссылки для скачивания (например, http: // localhost: 3000 / assets / get / 2 ), выйти из приложения и снова перейти по ссылке. Он должен попросить вас войти, прежде чем вы сможете скачать файл. Даже после того, как вы вошли в систему, если вы попытаетесь загрузить файл, который не принадлежит вам, вы получите сообщение об ошибке.
Мы разместим приятное сообщение для пользователей, которые пытаются получить доступ к файлам других людей. В том же действии get
assets_controller
измените код следующим образом:
1
2
3
4
5
6
7
|
asset = current_user.assets.find_by_id(params[:id])
if asset
send_file asset.uploaded_file.path, :type => asset.uploaded_file_content_type
else
flash[:error] = «Don’t be cheeky! Mind your own assets!»
redirect_to assets_path
end
|
Выше мы добавили флеш-сообщение. Чтобы попробовать это, войдите в систему и перейдите по URL-адресу, например, http: // localhost: 3000 / assets / get / 22 (к которому у вас не должно быть доступа). В результате вам будет представлено следующее:
Дальнейшие потенциальные улучшения
Описанный выше способ предоставления пользователям возможности загружать файлы, скорее всего, подойдет для приложения с небольшим количеством пользователей и трафиком. Но как только вы получаете все больше и больше запросов (для загрузки), приложение Rails будет страдать от использования метода send_file
по умолчанию, поскольку процесс Rails будет считывать весь файл, копируя содержимое файла в выходной поток по мере его поступления.
Однако это можно легко исправить с помощью X-Sendfile
. Это доступно как модуль apache . Вы можете узнать больше о том, как Rails обрабатывает загрузку файлов с помощью send_file
здесь .
Шаг 5 Интеграция Amazon S3
«Теперь мы рассмотрим, как хранить файлы на Amazon S3 вместо локального компьютера. Вы можете
skip
этот шаг и перейти к шагу 6, если вам не нужны эти функции».
Поскольку наше приложение предназначено для хранения файлов и обмена ими, мы не должны упускать возможность использовать Amazon S3 . В конце концов, большинство из нас уже используют это, не так ли?
Хранение файлов на вашем локальном компьютере (или на вашем сервере) ограничит применение, когда дело доходит до хранения данных. Давайте заставим это приложение использовать S3 для хранения файлов, чтобы предотвратить некоторые головные боли, которые могут возникнуть позже.
Как всегда, мы не хотим тратить время на изобретение колеса. Вместо этого, почему бы не использовать блестящий драгоценный камень под названием aws-s3
чтобы помочь Paperclip использовать Amazon S3 для хранения файлов. Давайте добавим это в наш Gemfile и запустим bundle install
в Terminal.
1
2
|
#for Paperclip to use Amazon S3
gem «aws-s3»
|
Далее, давайте изменим модель активов для конфигурации скрепки, чтобы дать ей указание хранить файлы в S3. Перейдите в app/models/asset.rb
и измените has_attached_file
, как has_attached_file
ниже:
1
2
3
4
5
|
has_attached_file :uploaded_file,
:path => «assets/:id/:basename.:extension»,
:storage => :s3,
:s3_credentials => «#{Rails_ROOT}/config/amazon_s3.yml»,
:bucket => «shareboxapp»
|
«Версия Paperclip, которую мы используем, похоже, не создает
bucket
если она не существует. Поэтомуcreate the bucket first
нужноcreate the bucket first
на Amazon S3».
:storage
говорит, что мы будем хранить файлы в S3. Вы должны указать название вашего сегмента в опции :bucket
. Конечно, S3 требует правильные учетные данные для загрузки файлов. Мы предоставим учетные данные в файле config/amazon_s3.yml
. Создайте этот файл и вставьте учетные данные. Пример файла приведен ниже:
01
02
03
04
05
06
07
08
09
10
11
|
development:
access_key_id: your_own_access_key_id
secret_access_key: your_own_secret_access_key
staging:
access_key_id: your_own_access_key_id
secret_access_key: your_own_secret_access_key
production:
access_key_id: your_own_access_key_id
secret_access_key: your_own_secret_access_key
|
«Пространство имен сегмента является общим для всех пользователей системы Amazon S3. Поэтому убедитесь, что вы используете
unique bucket name
».
Если вы перезапустите сервер Rails и загрузите файл, вы должны увидеть этот файл в корзине S3. Я использую удивительный плагин S3fox Firefox для просмотра моих папок и файлов S3.
Однако если вы перейдете на страницу индекса контроллера assets
и нажмете на недавно загруженный файл, вы столкнетесь с этой ошибкой.
Эта ошибка отмечает, что приложение не может найти файл в assets / 3 / Saturn.gif . Хотя мы указали, что в конфигурации Paperclip нам следует использовать S3 в качестве хранилища, мы все еще используем метод send_file
, который отправляет только локальные файлы с вашего компьютера (или с вашего сервера), а не из удаленного местоположения, например Amazon S3.
Давайте исправим это, изменив действие get
в assets_controller.rb
01
02
03
04
05
06
07
08
09
10
|
def get
asset = current_user.assets.find_by_id(params[:id])
if asset
#redirect to amazon S3 url which will let the user download the file automatically
redirect_to asset.uploaded_file.url, :type => asset.uploaded_file_content_type
else
flash[:error] = «Don’t be cheeky! Mind your own assets!»
redirect_to assets_path
end
end
|
Мы только что перенаправили ссылку на файл Amazon S3. Давайте посмотрим на это в действии, снова щелкнув ссылку на файл на странице индекса активов. После этого вы будете перенаправлены на фактическое местоположение файла, хранящегося на S3, например http://s3.amazonaws.com/shareboxapp/assets/3/Saturn.gif?1295915759 .
«Предоставление загружаемой ссылки с вашего Amazon s3 никогда не будет хорошей вещью, если только вы не намерены делиться ею с другими».
Как видите, на этот раз загружаемые ссылки больше не защищены. Вы даже можете загружать файлы без входа в систему. Мы должны что-то с этим сделать.
Есть несколько вариантов, доступных для нас.
- Мы можем сначала попытаться загрузить файл с S3 на сервер (или на ваш компьютер) и в фоновом режиме отправить файл пользователю.
- Мы могли бы передавать файл из S3, открывая его в процессе Rails, для отправки данных (файла) обратно пользователю.
Сейчас мы будем использовать первый вариант для быстрого прогресса, так как второй гораздо более продвинутый, чтобы понять его правильно, что будет выходить за рамки этого урока.
Итак, давайте снова изменим действие get
в assets_controller.rb.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
def get
asset = current_user.assets.find_by_id(params[:id])
if asset
#Parse the URL for special characters first before downloading
data = open(URI.parse(URI.encode(asset.uploaded_file.url)))
#then again, use the «send_data» method to send the above binary «data» as file.
send_data data, :filename => asset.uploaded_file_file_name
#redirect to amazon S3 url which will let the user download the file automatically
#redirect_to asset.uploaded_file.url, :type => asset.uploaded_file_content_type
else
flash[:error] = «Don’t be cheeky! Mind your own assets!»
redirect_to root_url
end
end
|
Мы добавили пару строк здесь. Первый используется для анализа строк URL-адреса Amazon s3 (для специальных символов, таких как пробелы и т. Д.) И открытия файла там с помощью метода « open
». Это вернет двоичные данные, которые мы затем можем отправить пользователю в виде файла, используя метод send_data
в следующей строке.
Если мы вернемся и снова щелкнем ссылку на файл, вы получите файл для загрузки, как показано ниже, без фактического URL-адреса Amazon s3.
Ну, так лучше, правда? Не совсем. Поскольку мы сначала открываем файл из Rails, перед тем как передать его пользователю, перед загрузкой файла может возникнуть значительная задержка. Но для простоты мы оставим все как есть.
На следующем шаге мы правильно перечислим файлы на домашней странице.
Шаг 6 Показать файлы на домашней странице
Давайте красиво перечислим файлы на домашней странице. Мы покажем файлы, когда пользователь перейдет к root_url
(который находится по адресу http: // localhost: 3000 ). Поскольку root_url
фактически указывает на действие Index
контроллера Home
, мы изменим действие сейчас.
1
2
3
4
5
|
def index
if user_signed_in?
@assets = current_user.assets.order(«uploaded_file_file_name desc»)
end
end
|
Выше мы загрузили переменную экземпляра @assets с собственными активами current_user — если пользователь вошел в систему. Мы также упорядочиваем файлы по «имени файла».
Затем в app/views/home/index.html.erb
вставьте следующее:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
<% unless user_signed_in?
<h1>Welcome to ShareBox</h1>
<p>File sharing web application you can’t ignore.</p>
<% else %>
<div class=»asset_list_header»>
<div class=»file_name_header»>File Name</div>
<div class=»file_size_header»>Size</div>
<div class=»file_last_updated_header»>Modified</div>
</div>
<div class=»asset_list»>
<% @assets.each do |asset|
<div class=»asset_details»>
<div class=»file_name»><%= link_to asset.file_name, download_url(asset) %></div>
<div class=»file_size»><%= asset.uploaded_file_file_size %></div>
<div class=»file_last_updated»><%= asset.uploaded_file_updated_at %></div>
</div>
<% end %>
</div>
<% end %>
|
Мы добавили условие, чтобы проверить, вошел ли пользователь в систему или нет. Если пользователь этого не сделал, он / она увидит:
Если пользователь вошел в систему, мы будем использовать @assets, которые мы установили в контроллере, чтобы пройти через него, чтобы показать File Name
File Size
и Last modified date time
. Скрепка предоставляет информацию о размере файла в виде [field_name] _file_size . Это предоставит вам общее количество bytes
файла. Под Last modified date time
мы понимаем здесь время, когда файл был загружен. Скрепка также предоставляет его как [field_name] _updated_at .
«Без правильного стиля то, что мы имеем здесь, выглядит довольно некрасиво. Поэтому давайте добавим немного CSS».
Теперь откройте файл public/stylesheets/application.css
и добавьте следующие стили в конец файла.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
.asset_list_header, .asset_list, .asset_details {
width:800px;
font-weight:bold;
font-size:12px;
overflow:hidden;
}
.file_name_header, .file_name {
width:350px;
float:left;
padding-left:20px;
}
.file_size_header, .file_size {
width:100px;
float:left;
}
.file_last_updated_header, .file_last_updated {
width:150px;
float:left;
}
.asset_list {
padding:20px 0;
}
.asset_details {
font-weight:normal;
height:25px;
line-height:25px;
border:1px solid #FFF;
width:790px;
color:#4F4F4F;
}
.asset_details a, .asset_details a:visited {
text-decoration:none;
color:#1D96EF;
font-size:12px;
}
|
Я не буду вдаваться в подробности здесь, поскольку это выходит за рамки этого урока.
Как вы видите, размер файла и последние измененные поля выглядят довольно странно в данный момент. Итак, давайте добавим метод в модель активов, чтобы помочь с информацией о размере файла. Перейдите в файл app/models/asset.rb
и добавьте этот метод.
1
2
3
|
def file_size
uploaded_file_file_size
end
|
Этот метод действует как псевдоним для uploaded_file_file_size
. Вместо этого вы также можете вызвать asset.file_size . Пока мы здесь, давайте сделаем байты более читабельными. На странице app/views/home/index.html.erb
измените asset.uploaded_file_file_size на:
1
|
number_to_human_size(asset.file_size, :precision => 2)
|
Мы только что использовали number_to_human_size
вида number_to_human_size
из Rails, чтобы помочь с информацией о размере файла.
Прежде чем обновить страницу, чтобы увидеть изменения, давайте добавим следующие строки в файл config/environment.rb
.
1
2
|
#Formatting DateTime to look like «20/01/2011 10:28PM»
Time::DATE_FORMATS[:default] = «%d/%m/%Y %l:%M%p»
|
Всегда перезапускайте сервер Rails, когда вносите изменения в файлы среды.
Перезапустите сервер Rails и обновите домашнюю страницу.
С домашней страницы мы ничего не можем сделать, кроме как для просмотра файлов. Давайте добавим кнопку «Загрузить» вверху.
01
02
03
04
05
06
07
08
09
10
11
12
|
<% unless user_signed_in?
<h1>Welcome to ShareBox</h1>
<p>File sharing web application you can’t ignore.</p>
<% else %>
<div id=»menu»>
<ul id= «top_menu»>
<li><%= link_to «Upload», new_asset_path %></li>
</ul>
</div>
…
<% end %>
|
Кнопка загрузки связана с созданием нового asset_path, как мы и хотели. Теперь мы добавим следующий CSS в файл application.css
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
#menu {
width:800px;
padding:0 20px;
margin:20px auto ;
overflow:hidden;
}
#menu ul{
padding:0;
margin:0;
}
#menu ul li {
list-style:none;
float:left;
display:block;
margin-right:10px;
}
#menu ul li a, #menu ul li a:visited{
display:block;
padding:0 15px;
line-height:25px;
text-decoration:none;
color:#45759F;
background:#EFF8FF;
border:1px solid #CFEBFF;
}
#menu ul li a:hover, #menu ul li a:active{
background:#DFF1FF;
border:1px solid #AFDDFF;
}
|
Если вы обновите страницу, она должна выглядеть следующим образом:
app/views/assets/new.html.erb
.
1
2
3
4
5
|
<% title «Upload a file» %>
<%= render ‘form’ %>
<p><%= link_to «Back to List», root_url %></p>
|
На следующем шаге мы узнаем, как создавать папки.
Шаг 7 Создание папок
Нам нужно организовать наши файлы и папки. Приложение должно предоставлять возможность пользователям создавать структуры папок и загружать в них файлы.
Мы можем создавать папки практически в представлениях, используя таблицу базы данных (модель), которая называется Папка. На самом деле мы не будем создавать папки в файловой системе или на Amazon S3. Мы в основном создадим концепцию и добавим функцию без особых усилий.
Давайте начнем с создания модели Folder
. Запустите следующую команду Scaffold в Терминале:
1
|
Rails g nifty:scaffold Folder name:string parent_id:integer user_id:integer
|
С помощью этой команды мы добавили name
для имени папки и user_id
для отношений с пользователем. Один пользователь имеет много папок, а одна папка принадлежит пользователю. Мы также добавили parent_id
для хранения вложенных папок.
Это поле parent_id
также является обязательным для гема » acts_as_tree «, который мы будем использовать для помощи с нашими вложенными папками. Теперь откройте только что созданный файл миграции и добавьте следующие индексы базы данных:
1
2
|
add_index :folders, :parent_id
add_index :folders, :user_id
|
Запустите » rake db:migrate
«, чтобы создать модель папки.
Затем перейдите к модели User
( app/models/user.rb
), чтобы обновить это.
1
|
has_many :folders |
И перейдите к app/models/folder.rb
модели Folder ( ), чтобы обновить его.
1
|
belongs_to :user
|
Давайте добавим acts_as_tree
драгоценный камень в Gemfile.
1
2
|
#For nested folders gem "acts_as_tree" |
Нам нужно добавить это в модель папки как часть настройки для acts_as_tree
.
1
2
3
4
|
class Folder < ActiveRecord::Base acts_as_tree …
end
|
Теперь запустите bundle install
и перезапустите сервер Rails.
«
acts_as_tree
позволяет использовать методы, такие какfolder.children
доступ к подпапкам иfolder.ancestors
доступ к корневым папкам».
Затем, внутри app/controllers/folders_controller.rb
, измените следующее, что позволяет защитить папки для пользователя, которому они принадлежат:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
class FoldersController < ApplicationController before_filter :authenticate_user ! def index
@folders = current_user.folders end
def show
@folder = current_user.folders.find(params[ :id ]) end
def new
@folder = current_user.folders. new end
def create
@folder = current_user.folders. new (params[ :folder ]) …
end
def edit
@folder = current_user.folders.find(params[ :id ]) end
def update
@folder = current_user.folders.find(params[ :id ]) …
end
def destroy
@folder = current_user.folders.find(params[ :id ]) …
end
end
|
Выше мы добавили before_filter :authenticate_user!
сверху, что требует от пользователей первого входа в систему, чтобы получить доступ. Во-вторых, мы изменили все Folder.new
или, Folder.find
чтобы current_user.folders.new
убедиться, что пользователь просматривает / получает доступ к папке, которой он владеет.
Давайте изменим представление об этом. Open app/views/folders/_form.html.erb
.
1
2
3
4
5
6
7
8
|
%>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
< p ><%= f.submit "Create Folder" %></ p > <% end %>
|
Здесь мы удалили два поля («user_id» и «parent_id»).
Теперь вы можете увидеть список папок по адресу http: // localhost: 3000 / folder . Вы также можете создавать новые папки по адресу http: // localhost: 3000 / folder / new . Далее мы разместим эти папки на домашней странице.
Шаг 8 Отображение папок на домашней странице
Чтобы отобразить папки на домашней странице, нам нужно загрузить папки в переменной экземпляра из контроллера. Идите и откройте, app/controllers/home_controller.rb
чтобы добавить это в index
действии.
1
2
3
4
5
6
7
8
9
|
def index
if user_signed_in? #load current_user's folders @folders = current_user.folders.order( "name desc" ) #load current_user's files(assets) @assets = current_user.assets.order( "uploaded_file_file_name desc" ) end
end
|
Мы добавили строку для загрузки папок пользователя в переменную экземпляра @folders .
Затем на app/views/home/index.html.erb
странице обновите код, как показано ниже, чтобы получить список папок.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
< div class = "asset_list" > <!-- Listing Folders --> %>
< div class = "asset_details folder" > < div class = "file_name" ><%= link_to folder.name, folder_path(folder) %></ div > < div class = "file_size" > - </ div > < div class = "file_last_updated" > - </ div > </div>
<% end %>
<!-- Listing Files --> %>
< div class = "asset_details file" > < div class = "file_name" ><%= link_to asset.file_name, download_url(asset) %></ div > < div class = "file_size" ><%= number_to_human_size(asset.file_size, :precision => 2) %></ div > < div class = "file_last_updated" ><%= asset.uploaded_file_updated_at %></ div > </div>
<% end %>
</div>
|
Мы использовали переменную @folders, чтобы вывести список папок и связать их с folder_path . Теперь, если вы обновите домашнюю страницу, вы должны увидеть что-то вроде:
Тем не менее, не очевидно, какая папка, а какая файл. Так как мы уже добавили классы CSS file
и folder
нам останется только взять некоторые изображения и использовать их в таблице стилей в качестве фоновых изображений.
Вы можете использовать любые изображения, которые вам нравятся. Для этого урока мы будем использовать некоторые из этих изображений . Загрузите их и поместите в public/images
папку. Далее давайте добавим немного CSS в application.css
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
.folder { background : url ( "../images/folder.png" ) no-repeat scroll left center transparent ; }
.file { background : url ( "../images/file.png" ) no-repeat scroll left center transparent ; }
.folder.asset_details:hover, .shared_folder.asset_details:hover, .file.asset_details:hover { border : 1px solid #DFDFDF ; }
.folder.asset_details:hover { background : url ( "../images/folder.png" ) no-repeat scroll left center #EFEFEF ; }
.shared_folder { background : url ( "../images/shared_folder.png" ) no-repeat scroll left center transparent ; }
.shared_folder.asset_details:hover { background : url ( "../images/shared_folder.png" ) no-repeat scroll left center #EFEFEF ; }
.file.asset_details:hover { background : url ( "../images/file.png" ) no-repeat scroll left center #EFEFEF ; }
|
Если вы обновите домашнюю страницу, вы должны увидеть значки папок и файлов, расположенные рядом с каждой вашей папкой и файлами.
Там нет кнопки «Новая папка». Давайте добавим его рядом с кнопкой «Загрузить», как показано ниже:
1
|
< li ><%= link_to "New Folder", new_folder_path %></ li > |
Большой!То, что у нас здесь, выглядит хорошо. Далее мы узнаем, как создавать вложенные папки и отображать хлебные крошки.
Шаг 9 Обработка вложенных папок и создание хлебных крошек
На этом шаге мы позволим пользователям создавать папки внутри других папок, используя acts_as_tree
гем, который мы установили на шаге 8.
Хотя мы будем предоставлять навигацию по хлебным крошкам вверху каждой страницы, мы упростим URL . Давайте создадим новый маршрут в config/routes.rb
файле. Поместите эту строку в верхней части страницы.
1
|
match "browse/:folder_id" => "home#browse" , :as => "browse" |
С этим маршрутом у нас теперь будут URL-адреса, такие как http: // localhost: 3000 / browse / 23, чтобы увидеть папку (с идентификатором 23). Он будет направлен на просмотр действий домашнего контроллера. Эта папка может быть вложена в другую, но нас это не волнует в URL. Вместо этого мы будем иметь дело с этим в контроллере. Добавьте следующий обзор в домашний контроллер.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
#this action is for viewing folders def browse #get the folders owned/created by the current_user @current_folder = current_user.folders.find(params[ :folder_id ]) if @current_folder #getting the folders which are inside this @current_folder @folders = @current_folder .children #We need to fix this to show files under a specific folder if we are viewing that folder @assets = current_user.assets.order( "uploaded_file_file_name desc" ) render :action => "index" else
flash[ :error ] = "Don't be cheeky! Mind your own folders!" redirect_to root_url end
end
|
@current_folder.children
даст вам все папки из которых@current_folder
является родителем .
Хорошо, давайте посмотрим на код выше, строка за строкой. Сначала мы определяем, является ли эта папка текущей / созданной пользователем current_user и помещаем ее в нее @current_folder
. Затем мы получаем все папки, которые находятся внутри этой папки.
Так как мы собираемся использовать app/views/home/index.html.erb
представление для рендеринга, нам нужно предоставить @assets
переменную. На данный момент мы перечисляем все активы, принадлежащие current_user, а не те, которые находятся в этой папке. Мы позаботимся об этом немного позже.
Наконец, если вы пытаетесь просмотреть папку, не принадлежащую вам, вы получите похожее сообщение, как и раньше, и будете перенаправлены обратно на домашнюю страницу.
В папке должно быть много файлов (ресурсов), и файл должен принадлежать папке. Поэтому нам нужно добавить folder_id
модель Asset. Запустите эту команду, чтобы создать файл миграции.
1
|
Rails g migration add_folder_id_to_assets folder_id :integer |
Это создаст новый файл миграции в db/migrate/
папке. Внутри этого файла мы добавим индекс для folder_id
, вот так.
01
02
03
04
05
06
07
08
09
10
|
class AddFolderIdToAssets < ActiveRecord::Migration def self.up
add_column :assets , :folder_id , :integer add_index :assets , :folder_id end
def self.down
remove_column :assets , :folder_id end
end
|
Далее беги rake db:migrate
. Это должно добавить новый столбец folder_id
в таблицу активов. Добавьте новое поле в модель актива ( app/models/asset.rb
). Кроме того, добавьте отношение к модели папки.
1
2
3
|
attr_accessible :user_id , :uploaded_file , :folder_id belongs_to :folder |
1
|
has_many :assets , :dependent => :destroy |
На этот раз мы добавили :dependent => :destroy
; это указывает Rails уничтожить все активы, которые принадлежат папке … как только папка будет уничтожена .
«Если вы не уверены , что нужно уничтожать и удалять в Rails, вы можете прочитать это ».
Вернемся к browse
действию в контроллере Home, давайте изменим способ установки @assets
переменной.
1
2
3
4
5
6
7
8
9
|
#this action is for viewing folders def browse …
#show only files under this current folder @assets = @current_folder .assets.order( "uploaded_file_file_name desc" ) …
end
|
browse
Действие теперь хорошо идти. Но нам нужно вернуться к
показатель
действие, потому что мы не хотим отображать все файлы и папки на странице индекса после того, как вы вошли в систему.
«Мы хотим отображать только корневые папки, у которых нет родителей, и только файлы, которые не находятся ни в каких папках».
Для этого изменим index
действие следующим образом:
1
2
3
4
5
6
7
8
9
|
def index
if user_signed_in? #show only root folders (which have no parent folders) @folders = current_user.folders.roots #show only root files which has no "folder_id" @assets = current_user.assets.where( "folder_id is NULL" ).order( "uploaded_file_file_name desc" ) end
end
|
Folder.roots
даст вам все корневые папки, которые не имеют родителей. Это одна из полезных областей применения, предоставляемых acts_as_tree
жемчужиной.
Мы также ставим условие для активов, чтобы захватить только те, которые не имеют folder_id
.
У нас есть структура для правильного отображения файлов и папок. Теперь нам нужно изменить ссылки на эти папки, чтобы перейти к browse_path
. На главной странице индекса измените ссылку следующим образом:
1
2
3
4
|
<!-- Listing Folders --> …
<div class = "file_name" ><%= link_to folder.name, browse_path(folder) %></div> …
|
Давайте начнем с создания нового маршрута внутри, routes.rb
чтобы разрешить создание «подпапок».
1
2
|
#for creating folders insiide another folder match "browse/:folder_id/new_folder" => "folders#new" , :as => "new_sub_folder" |
Выше мы добавили новый маршрут, названный new_sub_folder , который позволит использовать URL-адреса, такие как http: // localhost: 3000 / browse / 22 / new_folder . Это будет ссылаться на new
действие Folder
контроллера. По сути, мы будем создавать новую папку в папке (с идентификатором 22 или около того).
Теперь нам нужно создать New Folder
кнопку, которая будет направлять этот маршрут, если вы находитесь в другой папке (т.е. если вы находитесь по URL-адресам, таким как http: // localhost: 3000 / browse / 22 ). Давайте добавим условное изменение на главной странице индекса в New Folder button
.
1
2
3
4
5
|
<% if @current_folder %> <li><%= link_to "New Folder" , new_sub_folder_path( @current_folder ) %></li> <% else %>
<li><%= link_to "New Folder" , new_folder_path %></li> <% end %>
|
Мы определяем, @current_folder
существует ли. Если это так, мы знаем, что мы находимся в папке, таким образом, делая ссылку прямо на new_sub_folder_path
. В противном случае, это все еще пойдет в normal new_folder_path
.
Теперь пришло время изменить new
действие в контроллере папки.
01
02
03
04
05
06
07
08
09
10
11
12
|
def new
@folder = current_user.folders. new #if there is "folder_id" param, we know that we are under a folder, thus, we will essentially create a subfolder if params[ :folder_id ] #if we want to create a folder inside another folder #we still need to set the @current_folder to make the buttons working fine @current_folder = current_user.folders.find(params[ :folder_id ]) #then we make sure the folder we are creating has a parent folder which is the @current_folder @folder .parent_id = @current_folder .id end
end
|
Мы устанавливаем parent_id
равное current_folder id
для папки, которую мы собираемся создать.
1
2
3
4
5
6
7
8
9
|
%>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.hidden_field :parent_id %> < p ><%= f.submit "Create Folder" %></ p > <% end %>
|
Далее мы должны позаботиться о create
действии перенаправления на правильный путь после сохранения папки.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
def create
@folder = current_user.folders. new (params[ :folder ]) if @folder .save flash[ :notice ] = "Successfully created folder." if @folder .parent #checking if we have a parent folder on this one redirect_to browse_path( @folder .parent) #then we redirect to the parent folder else
redirect_to root_url #if not, redirect back to home page end
else
render :action => ‘new’
end
end
|
Этот код гарантирует, что пользователь правильно перенаправлен в папку, которую он просматривал перед созданием папки.
Наконец, давайте исправим ссылку «Вернуться к списку» на странице создания папки. Этот файл находится по адресу app/views/folders/new.html.erb
. Откройте его и измените его следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
<% title "New Folder" %> <%= render ‘form’ %>
<p>
<% if @folder.parent %> <%= link_to "Back to '#{@folder.parent.name}' Folder", browse_path(@folder.parent) %> <% else %>
<%= link_to "Back to Home page", root_url %> <% end %>
</p>
|
Страница должна теперь иметь необходимые ссылки, такие как Back to 'Documents' Folder
.
Теперь давайте создадим базовую навигацию по страницам.
Создайте частичный файл, вызываемый _breadcrumbs.html.erb
внутри app/views/home/
папки, и вставьте следующий код:
01
02
03
04
05
06
07
08
09
10
11
|
< div class = "breadcrumbs" > <% if @current_folder #checking if we are under any folder %> <%= link_to "ShareBox", root_url %> %>
» <%= link_to folder.name, browse_path(folder) %> <% end %>
» <%= @current_folder.name %> <% else #if we are not under any folder%> ShareBox <% end %>
</div>
|
»
@current_folder.ancestors
предоставляет нам массив папок, который содержит все родительские папки@current_folder
.»
В приведенном выше коде мы сначала определяем, находимся ли мы в каких-либо папках. Если нет (то есть, если мы находимся на домашней странице в первый раз), мы просто показываем текст (не ссылку) «ShareBox».
В противном случае мы отображаем ссылку «ShareBox», чтобы пользователи могли легко вернуться на домашнюю страницу. Затем мы используем ancestors
метод, чтобы получить папку родителей и перевернуть ее, чтобы отобразить их правильно.
Мы заканчиваем его именем current_folder, которое мы отображаем как обычный текст. Поскольку пользователь уже находится в этой папке, он не должен быть ссылкой.
Теперь давайте добавим немного CSS в файл application.css.
1
2
3
4
5
6
7
8
|
.breadcrumbs { margin:10px 0;
font-weight:bold;
}
.breadcrumbs a { color : #1D96EF ; text-decoration:none;
}
|
Наконец, мы должны вызвать партиал как на домашней странице, так и на странице создания папки. Перейдите app/views/home/index.html.erb
и добавьте следующее сразу после «меню» div
.
1
|
<%= render :partial => "breadcrumbs" %> |
Кроме того, добавьте следующее app/views/folders/new.html.erb
сразу после строки «title».
«дом / панировочные сухари»%>
Обратите внимание, что вы должны передать «домашний» каталог для вызова частичного из каталога «папки», потому что они оба находятся в одном каталоге. Давайте посмотрим некоторые скриншоты хлебных крошек в действии!
Эта навигация по крошкам должна быть вполне подходящей для наших нужд. Далее мы добавим возможность загружать и хранить файлы в папке.
Шаг 10 Загрузка файлов в папку
Аналогично созданию подпапки, мы будем использовать тот же тип маршрута для загрузки (под) файла в папку. Откройте routes.rb
и добавьте:
1
2
|
#for uploading files to folders match "browse/:folder_id/new_file" => "assets#new" , :as => "new_sub_file" |
Это создаст структуры URL, такие как http: // localhost: 3000 / browse / 22 / new_file . Это также повлияет на new
действие контроллера активов.
Измените Upload
кнопку на домашней странице, чтобы она указывала на правильный URL-адрес, если мы просматриваем папку. Поэтому отредактируйте ссылку «top_menu», как показано ниже:
1
2
3
4
5
6
7
8
9
|
< ul id = "top_menu" > <% if @current_folder %> < li ><%= link_to "Upload", new_sub_file_path(@current_folder) %></ li > < li ><%= link_to "New Folder", new_sub_folder_path(@current_folder) %></ li > <% else %>
< li ><%= link_to "Upload", new_asset_path %></ li > < li ><%= link_to "New Folder", new_folder_path %></ li > <% end %>
</ul>
|
Этот код сделает Upload
кнопку прямой new_asset_path
, если нет @current_folder
. Если есть, с другой стороны, он будет направлен на new_sub_file_path
.
Затем измените new
действие контроллера активов.
1
2
3
4
5
6
7
|
def new
@asset = current_user.assets.build if params[ :folder_id ] #if we want to upload a file inside another folder @current_folder = current_user.folders.find(params[ :folder_id ]) @asset .folder_id = @current_folder .id end
end
|
Давайте сделаем быстрое изменение app/views/assets/new.html.erb
. Нам нужно иметь правильный URL-адрес «Назад», а также панировочные сухари.
01
02
03
04
05
06
07
08
09
10
11
12
|
<% title "Upload a file" %> <%= render :partial => "home/breadcrumbs" %> <%= render ‘form’ %>
<p>
<% if @asset.folder %> <%= link_to "Back to '#{@asset.folder.name}' Folder", browse_path(@asset.folder) %> <% else %>
<%= link_to "Back", root_url %> <% end %>
</p>
|
Этот код должен выглядеть очень похоже на новую страницу папки. Мы добавили частичку хлебных крошек и персонализировали Back to
ссылку для перехода в родительскую папку.
Затем в app/views/assets/_form.html.erb
файле, добавьте скрытое поле, folder_id
.
01
02
03
04
05
06
07
08
09
10
|
%>
<%= f.error_messages %>
<p>
<%= f.label :uploaded_file, "File" %>< br /> <%= f.file_field :uploaded_file %> </p>
<%= f.hidden_field :folder_id %> < p ><%= f.submit "Upload" %></ p > <% end %>
|
Также в Asset
контроллере измените create
действие, например так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
def create
@asset = current_user.assets.build(params[ :asset ]) if @asset .save flash[ :notice ] = "Successfully uploaded the file." if @asset .folder #checking if we have a parent folder for this file redirect_to browse_path( @asset .folder) #then we redirect to the parent folder else
redirect_to root_url end
else
render :action => ‘new’
end
end
|
Этот код гарантирует, что пользователь будет перенаправлен обратно в нужную папку после завершения загрузки.
Теперь вы сможете загружать файлы в определенные папки. Дайте ему попытку убедиться, что все работает, прежде чем двигаться вперед.
Шаг 11 Добавьте действия к файлам и папкам
Теперь, когда у нас есть все файлы и папки, мы должны иметь возможность редактировать, удалять, загружать и делиться ими. Давайте начнем с создания некоторых ссылок на главной странице.
«Нам нужно , чтобы позволить пользователям сделать три вещи в папки:
Share
,Rename
и ,Delete
однако, для файлов, мы только позволяют пользователю выполнять две вещи:.Download
ИDelete
»
Вернитесь на домашнюю страницу и измените список папок следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
|
<!-- Listing Folders --> %>
< div class = "asset_details folder" > < div class = "file_name" ><%= link_to folder.name, browse_path(folder) %></ div > < div class = "file_size" >-</ div > < div class = "file_last_updated" >-</ div > < div class = "actions" > < div class = "share" > <%= link_to "Share" %> </div>
< div class = "rename" > <%= link_to "Rename" %> </div>
< div class = "delete" > <%= link_to "Delete" %> </div>
</div>
</div>
<% end %>
|
Давайте также сделаем то же самое для списка файлов:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
<!-- Listing Files --> %>
< div class = "asset_details file" > < div class = "file_name" ><%= link_to asset.file_name, download_url(asset) %></ div > < div class = "file_size" ><%= number_to_human_size(asset.file_size, :precision => 2) %></ div > < div class = "file_last_updated" ><%= asset.uploaded_file_updated_at %></ div > < div class = "actions" > < div class = "download" > <%= link_to "Download" %> </div>
< div class = "delete" > <%= link_to "Delete" %> </div>
</div>
</div>
<% end %>
|
Мы добавили ссылки для скачивания и удаления выше. Почему бы не добавить CSS, чтобы эти ссылки выглядели немного лучше? Добавьте следующее в application.css
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
.actions { float:right;
font-size:11px;
}
.share, .rename, .download, .delete{ float:left;
}
.asset_details .share a,.asset_details .rename a,.asset_details .download a,.asset_details .delete a{ border : 1px solid #CFE9FF ; font-size: 11px;
margin-left: 5px;
padding : 0 2px ; }
.asset_details .share a:hover,.asset_details .rename a:hover,.asset_details .download a:hover,.asset_details .delete a:hover{ border : 1px solid #8FCDFF ; }
.asset_details .delete a{ color : #BF0B12 ; border : 1px solid #FFCFD1 ; }
.asset_details .delete a:hover{ color : #BF0B12 ; border : 1px solid #FF8F93 ; }
|
Удалить файлы
Теперь давайте установим ссылки для действий. Начнем с ссылок на удаление активов (файлов). Измените delete
ссылку на файл на домашней странице следующим образом:
1
|
<%= link_to "Delete", asset, :confirm => 'Are you sure?', :method => :delete %> |
Появится подтверждающее сообщение, как только вы нажмете на ссылку, спрашивающую, уверены ли вы, что хотите удалить ее. Затем он направит вас к действию уничтожения контроллера активов. Итак, давайте изменим это действие, чтобы перенаправить:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
def destroy
@asset = current_user.assets.find(params[ :id ]) @parent_folder = @asset .folder #grabbing the parent folder before deleting the record @asset .destroy flash[ :notice ] = "Successfully deleted the file." #redirect to a relevant path depending on the parent folder if @parent_folder redirect_to browse_path( @parent_folder ) else
redirect_to root_url end
end
|
«Обратите внимание, что нам нужно получить
@parent_folder
файл до того, как он будет удален».
Удалить папки
Теперь пришло время изменить ссылку удаления папки на домашней странице следующим образом:
1
|
<%= link_to "Delete", folder, :confirm => 'Are you sure to delete the folder and all of its contents?', :method => :delete %> |
«Помните: всякий раз, когда мы удаляем папку, нам также необходимо удалять содержимое (файлы и папки) внутри нее».
А вот и действие «Разрушить» контроллера папок. Поэтому мы будем редактировать код там.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
def destroy
@folder = current_user.folders.find(params[ :id ]) @parent_folder = @folder .parent #grabbing the parent folder #this will destroy the folder along with all the contents inside #sub folders will also be deleted too as well as all files inside @folder .destroy flash[ :notice ] = "Successfully deleted the folder and all the contents inside." #redirect to a relevant path depending on the parent folder if @parent_folder redirect_to browse_path( @parent_folder ) else
redirect_to root_url end
end
|
Это Сэм, как мы сделали в контроллере активов. Обратите внимание, что при уничтожении папки все файлы и папки будут автоматически уничтожены, потому что мы определили :dependent => :destroy
модель папки для всех файлов. Также acts_as_tree
уничтожит все дочерние папки, по умолчанию.
Скачать файлы
Создать ссылку «Скачать файл» очень просто. Измените Download
ссылку в списке файлов на домашней странице.
1
|
<%= link_to "Download", download_url(asset) %> |
download_url(asset)
Уже используется на ссылку имени файла; так что это не должно быть сюрпризом для вас.
Переименовать папки
Переименовать папку — значит отредактировать ее. Поэтому нам нужно перенаправить ссылку, чтобы перейти к действию Edit контроллера Folder. Давайте сделаем это с новым маршрутом для обработки идентификаторов вложенных папок. Мы можем создать этот новый маршрут в routes.rb
файле.
1
2
|
#for renaming a folder match "browse/:folder_id/rename" => "folders#edit" , :as => "rename_folder" |
Затем измените ссылку «Переименовать» в списке папок на домашней странице.
1
|
<%= link_to "Rename", rename_folder_path(folder) %> |
В контроллере папок измените действие «Редактировать» следующим образом:
1
2
3
4
|
def edit
@folder = current_user.folders.find(params[ :folder_id ]) @current_folder = @folder .parent #this is just for breadcrumbs end
|
«В
@current_folder
Edit действие не может иметь смысл сначала, но нам нужна переменная экземпляра для отображения сухари правильно.»
Далее обновите app/views/folders/edit.html.erb
страницу:
01
02
03
04
05
06
07
08
09
10
11
12
|
<% title "Rename Folder" %> <%= render :partial => "home/breadcrumbs" %> <%= render ‘form’ %>
<p>
<% if @folder.parent %> <%= link_to "Back to '#{@folder.parent.name}' Folder", browse_path(@folder.parent) %> <% else %>
<%= link_to "Back", root_url %> <% end %>
</p>
|
Мы просто добавили панировочные сухари и обновили заголовок и Back to
ссылку.
Теперь нам нужно всего лишь изменить кнопку «Создать папку» на «Переименовать папку». Итак, измените app/views/folders/_form.html.erb
на:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
%>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.hidden_field :parent_id %> <p>
%>
<%= f.submit "Create Folder" %> <% else %>
<%= f.submit "Rename Folder" %> <% end %>
</p>
<% end %>
|
Страница должна теперь выглядеть как на следующем изображении, когда вы нажимаете, чтобы переименовать папку.
Шаг 12 Совместное использование папок между пользователями
«В этом приложении ShareBox нам нужно сделать папки общими для всех. Любое содержимое (файлы и / или папки) внутри общей папки будет доступно каждому общему пользователю».
Создание структуры
Мы упростим процесс обмена с помощью нескольких простых шагов. Наиболее вероятный сценарий, когда пользователь совместно использует папку:
- Пользователь нажимает на ссылку « Поделиться» для папки
- Появится диалоговое окно, позволяющее пользователю ввести адреса электронной почты, чтобы поделиться папкой с
- Пользователь может добавить дополнительное сообщение к приглашению, если он пожелает
- Как только пользователь пригласит человека (адрес электронной почты), мы сохраним эту информацию в БД, чтобы проинформировать систему о том, чтобы у этого владельца адреса электронной почты был доступ к общей папке.
- Общему пользователю будет отправлено электронное письмо для информирования о приглашении.
- Затем общий пользователь входит (или сначала регистрируется, а затем входит), и он / она увидит общую папку.
- Разделяемый пользователь не может выполнять никаких действий с общими папками, кроме загрузки файлов в них.
Нам нужна новая модель обработки общих папок. Давайте назовем это SharedFolder
. Запустите следующий скрипт генерации модели в Терминале.
1
|
Rails g model SharedFolder user_id :integer shared_email :string shared_user_id :integer folder_id :integer message :string |
Это user_id
для владельца общей папки. Для shared_user_id
пользователя, которому владелец предоставил доступ к папке. shared_email
Для адреса электронной почты в shared_user. folder_id
, Очевидно , папка общего доступа. message
Для необязательное сообщение будет отправлено на приглашение по электронной почте.
Во вновь созданном файле миграции (в db/migrate
папке) добавьте следующие индексы:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
class CreateSharedFolders < ActiveRecord::Migration def self.up
create_table :shared_folders do |t| t.integer :user_id t.string :shared_email t.integer :shared_user_id t.integer :folder_id t.string :message t.timestamps
end
add_index :shared_folders , :user_id add_index :shared_folders , :shared_user_id add_index :shared_folders , :folder_id end
def self.down
drop_table :shared_folders end
end
|
Теперь беги rake db:migrate
. Это создаст таблицу БД для вас.
В модель SharedFolder, расположенную по адресу app/models/shared_folder.rb
, добавьте следующее:
01
02
03
04
05
06
07
08
09
10
11
12
|
class SharedFolder < ActiveRecord::Base attr_accessible :user_id , :shared_email , :shared_user_id , :message , :folder_id #this is for the owner(creator) of the assets belongs_to :user
#this is for the user to whom the owner has shared folders to belongs_to :shared_user , :class_name => "User" , :foreign_key => "shared_user_id" #for the folder being shared belongs_to :folder end
|
«Мы связываемся с
User
моделью дважды изShareFolder
модели. Дляshared_user
соединения нам нужно указать, какой класс и какой внешний ключ используется, потому что он не следует соглашениям Rails по умолчанию для связи сUser
моделью».
Теперь в User
модели, давайте добавим эти два отношения:
1
2
3
4
5
|
#this is for folders which this user has shared has_many :shared_folders , :dependent => :destroy #this is for folders which the user has been shared by other users has_many :being_shared_folders , :class_name => "SharedFolder" , :foreign_key => "shared_user_id" , :dependent => :destroy |
Затем в модели папки добавьте это отношение.
1
|
has_many :shared_folders , :dependent => :destroy |
Создание представления для обмена
«Мы собираемся использовать jQuery и jQuery UI для создания формы приглашения для совместного использования папок».
Прежде всего, мы используем jQuery вместо Prototype, чтобы предотвратить возможность CSRF-атак. По умолчанию Rails использует библиотеку Prototype JS, чтобы помочь с этим.
Нам нужно сначала скачать версию jQuery здесь . Вам следует скачать zip-файл, распаковать его, скопировать jquery.Rails.js
и вставить в public/javascripts
папку приложения Rails .
Затем мы должны использовать этот файл JS вместо файла по умолчанию. Откройте app/views/layout/application.html.erb
файл и измените следующее в разделе «head».
01
02
03
04
05
06
07
08
09
10
11
|
<head>
< title >ShareBox |<%= content_for?(:title) ? yield(:title) : "Untitled" %></ title > <%= stylesheet_link_tag "application" %> —>
<%= javascript_include_tag "jquery.Rails" %> <%= csrf_meta_tag %> <%= yield(:head) %>
</head>
|
Нам нужно скачать jQuery UI отсюда . Убедитесь, что вы выбрали Redmond
тему в соответствии с нашими цветами.
Загрузив его, скопируйте файлы jquery-1.4.4.min.js и jquery-ui-1.8.9.custom.min.js и вставьте их в public/javascripts
папку.
Также скопируйте всю папку Redmond из папки CSS вpublic/stylesheets
Далее нам нужно загрузить файлы в нашем файле приложения макета ( app/views/layouts/application.html.erb
) в разделе «head».
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<head>
< title >ShareBox |<%= content_for?(:title) ? yield(:title) : "Untitled" %></ title > <%= stylesheet_link_tag "application", "redmond/jquery-ui-1.8.9.custom" %> <%= javascript_include_tag "jquery-1.4.4.min", "jquery-ui-1.8.9.custom.min" %> <%= javascript_include_tag "application" %> —>
<%= javascript_include_tag "jquery.Rails" %> <%= csrf_meta_tag %> <%= yield(:head) %>
</head>
|
application.js
Файл также загружается здесь. В скором времени мы поместим наш собственный код JavaScript в этот файл.
Теперь мы создадим диалоговое окно jQuery UI, чтобы помочь с формой приглашения. Мы загрузим его, когда нажмем кнопку «Поделиться».
Сначала откройте app/views/home/index.html.erb
страницу и добавьте следующее в нижней части страницы, перед последней ,
01
02
03
04
05
06
07
08
09
10
|
< div id = "invitation_form" title = "Invite others to share" style = "display:none" > <% form_tag '/home/share' do -%> < label for = "email_addresses" >Enter recipient email addresses here</ label >< br /> <%= text_field_tag 'email_addresses', "", :class => 'text ui-widget-content ui-corner-all'%> <br /><br />
< label for = "message" >Optional message</ label >< br /> <%= text_area_tag 'message',"", :class => 'text ui-widget-content ui-corner-all'%> <%= hidden_field_tag "folder_id" %> <% end -%> </div>
|
Выше мы создали HTML-форму с одним текстовым полем для адреса электронной почты и текстовой областью для необязательного сообщения. Также обратите внимание, что мы добавили скрытое поле с именем «folder_id», которое мы будем использовать для публикации формы позже.
По умолчанию div скрыт и будет отображаться в диалоговом окне при нажатии кнопки «Поделиться».
Мы собираемся разместить триггер в нашем коде JavaScript. Перед тем , как сделать это, однако, мы должны иметь конкретный идентификатор папки и имя папки для того, чтобы пользователь хочет поделиться. Чтобы получить это, нам нужно изменить ссылку на папку общего доступа в списке действий папки. На домашней странице в списке папок измените ссылку «Поделиться», например:
1
|
<%= link_to "Share", "#", :folder_id => folder.id, :folder_name => folder.name %> |
Теперь ссылка будет сгенерирована:
1
|
< a folder_name = "Documents" folder_id = "1" href = "#" >Share</ a > |
Каждая ссылка «Поделиться» для каждой папки теперь будет иметь уникальный атрибут folder_name и folder_id.
Затем мы создадим событие триггера в public/javascripts/application.js
файле:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
$(function () {
//open the invitation form when a share button is clicked $( ".share a" ) .button()
.click( function () { //assign this specific Share link element into a variable called "a" var a = this ; //First, set the title of the Dialog box to display the folder name $( "#invitation_form" ).attr( "title" , "Share '" + $(a).attr( "folder_name" ) + "' with others" ); //a hack to display the different folder names correctly $( "#ui-dialog-title-invitation_form" ).text( "Share '" + $(a).attr( "folder_name" ) + "' with others" ); //then put the folder_id of the Share link into the hidden field "folder_id" of the invite form $( "#folder_id" ).val($(a).attr( "folder_id" )); //Add the dialog box loading here return false;
});
});
|
Этот код указывает, что при каждом нажатии на каждую ссылку с CSS-классом «share» мы запускаем анонимную функцию. Обратитесь к комментариям выше для получения дополнительной информации.
Теперь нам нужно добавить следующий код вместо того, //Add the dialog box loading here
чтобы фактически загрузить диалоговое окно.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
//the dialog box customization $( "#invitation_form" ).dialog({ height: 300, width: 600,
modal: true,
buttons: {
//First button "Share" : function () { //get the url to post the form data to var post_url = $( "#invitation_form form" ).attr( "action" ); //serialize the form data and post it the url with ajax $.post(post_url,$( "#invitation_form form" ).serialize(), null , "script" ); return false;
},
//Second button Cancel: function() {
$( this ).dialog( "close" ); }
},
close: function() {
}
});
|
Диалоговое окно должно быть загружено с указанной шириной и высотой, а также имеет две кнопки: «Поделиться» и «Отмена». Когда кнопка «Поделиться» нажата, мы выполним два действия. Сначала мы сохраняем действие формы («home / share») в переменной с именем post_url
. Во-вторых, мы публикуем форму, используя AJAX, post_url
после сериализации данных формы.
«Последний скрипт значения параметра метода AJAX, $ .post () , инструктирует Rails отвечать, когда запрос AJAX завершен».
Прежде чем мы протестируем это в нашем браузере, давайте добавим немного стиля в файл application.css.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
.ui-button-text-only .ui-button-text { font-size : 14px ; padding : 3px 10px !important ; }
.share .ui-button-text-only .ui-button-text { padding : 0 2px !important ; font-size:11px;
font-weight:normal;
}
#invitation_form{ font-size:14px;
}
#invitation_form label{ font-weight:bold;
}
#invitation_form input.text { width:480px;
height:20px;
font-size:12px;
color : #7F7F7F ; }
#invitation_form textarea { width:480px;
height:100px;
font-size:12px;
color : #7F7F7F ; }
|
Так как в настоящее время у нас нет подходящего маршрута для home / share , отправка формы в фоновом режиме завершится сбоем, так как это AJAX-запрос.
Мы создадим новый маршрут в routes.rb
файле.
1
2
|
#for sharing the folder match "home/share" => "home#share" |
Этот маршрут будет направлен на действие Share контроллера Home. Итак, давайте создадим это действие сейчас.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#this handles ajax request for inviting others to share folders def share #first, we need to separate the emails with the comma email_addresses = params[ :email_addresses ].split( "," ) email_addresses. each do |email_address| #save the details in the ShareFolder table @shared_folder = current_user.shared_folders. new @shared_folder .folder_id = params[ :folder_id ] @shared_folder .shared_email = email_address #getting the shared user id right the owner the email has already signed up with ShareBox #if not, the field "shared_user_id" will be left nil for now. shared_user = User.find_by_email(email_address) @shared_folder .shared_user_id = shared_user.id if shared_user @shared_folder .message = params[ :message ] @shared_folder .save #now we need to send email to the Shared User end
#since this action is mainly for ajax (javascript request), we'll respond with js file back (refer to share.js.erb) respond_to do |format|
format.js { }
end
end
|
Здесь мы разделяем адреса электронной почты запятыми, поскольку мы можем совместно использовать одну папку с несколькими адресами электронной почты. Затем мы перебираем каждый адрес электронной почты, чтобы создать запись SharedFolder . Мы уверены, что у нас есть правильный идентификатор пользователя (владельца) и идентификатор папки.
«Получить общего пользователя немного сложно. Владелец адреса электронной почты может быть, а может и не быть владельцем аккаунта этого приложения ShareBox».
Чтобы компенсировать это, мы возьмем shared_user
единственное, когда сможем найти владельца адреса электронной почты. Если мы не сможем найти его, мы оставим это значение как нулевое shared_user_id
. Мы позаботимся об этом немного позже. Мы также сохраним необязательное сообщение.
Теперь давайте создадим файл «share.js.erb» в app/views/home
папке, так как AJAX-запрос будет ожидать ответа JavaScript.
1
2
3
4
5
6
7
8
|
//closing the dialog box $( "#invitation_form" ).dialog( "close" ); //making sure we don't display the flash notice more than once $( "#flash_notice" ).remove(); //showing a flash message $( "#container" ).prepend( "<div id='flash_notice'>Successfully shared the folder</div>" ); |
Сначала мы закрываем диалоговое окно. Затем мы удаляем все существующие Flash-сообщения и, наконец, отображаем новое Flash-сообщение, информирующее пользователя о том, что папка открыта для общего доступа.
Но я хочу, чтобы значки общих папок отличались от обычных значков папок. Во-первых, мне нужно знать, является ли папка общей или нет. Мы можем добавить быстрый метод к модели папки, чтобы помочь с этой задачей.
1
2
3
4
|
#a method to check if a folder has been shared or not def shared? ! self .shared_assets.empty? end
|
Мы можем определить,
a_folder
является ли это общим или нет, с помощью вызоваa_folder.shared?
, который возвращает логическое значение.
Далее нам нужно знать, какой элемент папки изменить в нашем jQuery. Таким образом, мы можем добавить folder_id
к каждой строке папки. На домашней странице / странице индекса обновите следующую строку:
1
|
< div class = "asset_details folder" > |
… и заменить его на:
1
|
< div class="asset_details <%= folder.shared? ? 'shared_folder' : 'folder' %>" id="folder_<%= folder.id %>"> |
Это назначает новый класс CSS, называемый «shared_folder», div
если папка является общей. Также мы добавляем folder_id, чтобы позволить jQuery динамически изменять значки, переключая класс CSS.
Фактически, мы уже добавили класс CSS «shared_folder» ранее. Так что это должно выглядеть примерно так, как только вы поделитесь папкой.
Добавьте следующие две строки в, share.js.erb
чтобы динамически изменять значки папок после запроса Ajax.
1
2
3
4
5
|
//Removing the css class 'folder' $( "#folder_<%= params[:folder_id] %>" ).removeClass( "folder" ); //Adding the css class 'shared_folder' $( "#folder_<%= params[:folder_id] %>" ).addClass( "shared_folder" ); |
Прежде чем мы вернемся к работе с разделом электронной почты, нам нужно поместить метод after_create в модель User, которая будет выполняться каждый раз, когда добавляется новый пользователь. Это будет синхронизировать новый идентификатор пользователя с SharedFolder, shared_user_id
если адрес электронной почты совпадает. Мы добавляем это в модель User.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
after_create :check_and_assign_shared_ids_to_shared_folders #this is to make sure the new user ,of which the email addresses already used to share folders by others, to have access to those folders def check_and_assign_shared_ids_to_shared_folders #First checking if the new user's email exists in any of ShareFolder records shared_folders_with_same_email = SharedFolder.find_all_by_shared_email( self .email) if shared_folders_with_same_email #loop and update the shared user id with this new user id shared_folders_with_same_email. each do |shared_folder| shared_folder.shared_user_id = self .id shared_folder.save end
end
end
|
Здесь мы получаем новый идентификатор пользователя с целью помещения его в shared_user_id
объект SharedFolder, если какая-либо из записей имеет тот же адрес электронной почты, что и новый пользователь. Это должно синхронизировать его с пользователями, с которыми вы пытаетесь открыть общий доступ к своей папке на ShareBox.
Шаг 13 Отправка писем общим пользователям
Теперь мы рассмотрим, как отправлять электронные письма, когда пользователь делится папкой с другими.
«Отправлять электронную почту в Rails 3 довольно просто. Мы будем использовать Gmail, поскольку вы можете легко и быстро создавать учетные записи Gmail, чтобы протестировать этот фрагмент руководства».
Во-первых, нам нужно добавить настройки SMTP в систему. Итак, создайте файл с именем «setup_mail.rb» внутри config/initializers
папки. Добавьте в файл следующие настройки:
1
2
3
4
5
6
7
8
9
|
ActionMailer::Base.smtp_settings = { :address => "smtp.gmail.com" , :port => 587 , :domain => "gmail.com" , :user_name => "shareboxapp" , :password => "secret" , :authentication => "plain" , :enable_starttls_auto => true }
|
«Не забудьте перезапустить сервер Rails, чтобы перезагрузить эти настройки».
Далее нам нужно создать почтовые объекты и представления. Давайте назовем это «UserMailer». Запустите следующий генератор:
1
|
Rails g mailer user_mailer |
Эта команда создаст app/mailers/user_mailer.rb
файл среди других. Давайте отредактируем файл, чтобы создать новый шаблон электронной почты.
1
2
3
4
5
6
7
8
9
|
class UserMailer < ActionMailer::Base def invitation_to_share(shared_folder) @shared_folder = shared_folder #setting up an instance variable to be used in the email template mail( :to => @shared_folder .shared_email, :subject => "#{@shared_folder.user.name} wants to share '#{@shared_folder.folder.name}' folder with you" ) end
end
|
Вы можете установить значения по умолчанию с помощью default
метода там. Мы в основном создадим метод, который принимает shared_folder
объект и передает его шаблону электронной почты, который мы создадим следующим.
Теперь мы должны создать шаблон электронной почты. Имя файла должно совпадать с именем метода, которое вы используете в UserMailer. Итак, давайте создадим invitation_to_share.text.erb
файл в app/views/user_mailer
папке.
Мы называем это как invitation_to_share.text.erb
использовать текстовые электронные письма. Если вы хотите использовать электронную почту на основе HTML, назовите ее,invitation_to_share.html.erb
Вставьте следующую формулировку.
01
02
03
04
05
06
07
08
09
10
11
12
|
Hey,
<%= @shared_folder.user.name %> has shared the "<%= @shared_folder.folder.name %>" folder on Sharebox. Message from <%= @shared_folder.user.name %>: "<%= @shared_folder.message %>" You can now login at <%= new_user_session_url %> and view the folder if you have an account already. If you don't have one, you can sign up here at <%= new_user_registration_url %> Have fun, Sharebox |
Чтобы это работало, мы добавили следующий код в share
действие контроллера Home, где мы оставили место для отправки электронных писем.
1
2
|
#now send email to the recipients UserMailer.invitation_to_share( @shared_folder ).deliver |
Теперь, если вы поделитесь папкой, она отправит электронное письмо общему пользователю. После того, как вы нажмете кнопку «Поделиться» в диалоговом окне, вам, возможно, придется подождать несколько секунд, чтобы система отправила электронное письмо пользователю, прежде чем вы увидите флэш-сообщение.
Шаг 14 Предоставление доступа к общей папке другим пользователям
Пока что, хотя пользователь может поделиться своей папкой с другими, они еще не увидят его на своих страницах ShareBox ».
Чтобы это исправить, нам нужно отобразить общие папки для общих пользователей на домашней странице. Мы определенно должны передать новую переменную экземпляра, загруженную этими общими папками, на страницу индекса.
Обновите действие index в контроллере Home следующим образом:
01
02
03
04
05
06
07
08
09
10
11
|
def index
if user_signed_in? #show folders shared by others @being_shared_folders = current_user.shared_folders_by_others #show only root folders @folders = current_user.folders.roots #show only root files @assets = current_user.assets.where( "folder_id is NULL" ).order( "uploaded_file_file_name desc" ) end
end
|
В этом коде мы добавили @being_shared_folders
переменные экземпляра. Обратите внимание, что в настоящее время у нас нет отношения, называемого shared_folders_by_others
, в User
модели; Итак, давайте добавим это сейчас.
1
2
|
#this is for getting Folders objects which the user has been shared by other users has_many :shared_folders_by_others , :through => :being_shared_folders , :source => :folder |
Теперь давайте покажем этот список общих папок на домашней странице / странице индекса. Вставьте следующий код прямо перед обычным списком папок:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<!-- Listing Shared Folders (the folders shared by others) --> %>
< div class="asset_details <%= folder.shared? ? 'shared_folder' : 'folder' %>" id="folder_<%= folder.id %>"> < div class = "file_name" ><%= link_to folder.name, browse_path(folder) %></ div > < div class = "file_size" >-</ div > < div class = "file_last_updated" >-</ div > < div class = "actions" > </div>
</div>
<% end %>
<!-- Listing Folders --> …
|
«Обратите внимание, что мы не будем предоставлять никаких ссылок действий для общих папок, поскольку они не принадлежат общему пользователю».
Это должно хорошо работать на домашней странице. Теперь вы сможете видеть чужие общие папки вверху страниц.
Но если вы находитесь в подпапке (своей), вы получите сообщение nil object
об ошибке @being_shared_folders
. Мы должны это исправить, и можем сделать это в browse
действии Home
контроллера. Посетите эту страницу и добавьте:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
#this action is for viewing folders def browse #making sure that this folder is owned/created by the current_user @current_folder = current_user.folders.find(params[ :folder_id ]) if @current_folder #if under a sub folder, we shouldn't see shared folders @being_shared_folders = [] …
else
…
end
end
|
Этот код гарантирует, что мы не хотим видеть папки, открытые другим пользователям. Эти папки должны существовать только на уровне Root. Выше должно решить эту проблему.
Просмотр папки, доступной другим
Теперь, если вы попытаетесь перейти в папку, доступную для других, вы получите эту ошибку:
Это связано с тем, что система считает, что вы пытаетесь получить доступ к папке, для которой у вас нет необходимых привилегий. Причина, по которой мы видим эту ошибку вместо сообщения « Не будь дерзким! », Заключается в том, что мы использовали find()
метод вместо find_by_id()
получения @current_folder
во время browse
действия Home
контроллера.
Теперь еще раз, мы должны переосмыслить логику. Нам нужно установить @current_folder
для папок, совместно используемых другими, но мы также должны передать какой-то вид в представления, который указывает, что эта папка является общей для других. Таким образом, представления могут ограничивать права доступа — такие как удаление папок и т. Д.
Для этого нам понадобится метод для User
модели, который определяет, имеет ли пользователь «Общий доступ» к указанной папке. Итак, давайте сначала добавим это, прежде чем мы изменим это в Browse
действии.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
|
#to check if a user has acess to this specific folder def has_share_access?(folder) #has share access if the folder is one of one of his own return true if self .folders.include?(folder) #has share access if the folder is one of the shared_folders_by_others return true if self .shared_folders_by_others.include?(folder) #for checking sub folders under one of the being_shared_folders return_value = false folder.ancestors. each do |ancestor_folder| return_value = self .being_shared_folders.include?(ancestor_folder) if return_value #if it's true return true
end
end
return false
end
|
Опять же, этот метод определяет, имеет ли пользователь общий доступ к папке. Пользователь имеет общий доступ, если он / она владеет им. Кроме того, пользователь имеет доступ к общим ресурсам, если папка является одной из папок пользователя, используемых другими пользователями.
Если ничего из вышеперечисленного не вернулось true
, нам все равно нужно проверить еще одну вещь. Хотя просматриваемая папка может не являться папкой, доступной для других, она все же может быть подпапкой одной из папок, доступных другим, и в этом случае пользователь действительно имеет общий доступ.
Таким образом, приведенный выше код проверяет предков папки для проверки этого.
Вернуться к просмотру действий Home
контроллера; вставьте туда следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
def browse #first find the current folder within own folders @current_folder = current_user.folders.find_by_id(params[ :folder_id ]) @is_this_folder_being_shared = false if @current_folder #just an instance variable to help hiding buttons on View #if not found in own folders, find it in being_shared_folders if @current_folder . nil ? folder = Folder.find_by_id(params[ :folder_id ]) @current_folder ||= folder if current_user.has_share_access?(folder) @is_this_folder_being_shared = true if @current_folder #just an instance variable to help hiding buttons on View end
if @current_folder #if under a sub folder, we shouldn't see shared folders @being_shared_folders = [] #show folders under this current folder @folders = @current_folder .children #show only files under this current folder @assets = @current_folder .assets.order( "uploaded_file_file_name desc" ) render :action => "index" else
flash[ :error ] = "Don't be cheeky! Mind your own assets!" redirect_to root_url end
end
|
Этот код выполняет две основные вещи:
- Назначает
@current_folder
, даже если вы находитесь в подпапке папки, совместно используемой другими - Назначает флаг,
@is_this_folder_being_shared
чтобы передать его представлениям. Это сообщит нам,@current_folder
является ли папка общей для других или нет.
Загрузка файлов из общих папок
Если вы попытаетесь загрузить файл из папки, к которой открыт доступ другим пользователям, вы увидите сообщение « Не будь дерзким! » Итак, давайте теперь настроим get
действие Asset
контроллера.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
def get #first find the asset within own assets asset = current_user.assets.find_by_id(params[ :id ]) #if not found in own assets, check if the current_user has share access to the parent folder of the File asset ||= Asset.find(params[ :id ]) if current_user.has_share_access?(Asset.find_by_id(params[ :id ]).folder) if asset #Parse the URL for special characters first before downloading data = open( URI .parse( URI .encode(asset.uploaded_file.url))) send_data data, :filename => asset.uploaded_file_file_name #redirect_to asset.uploaded_file.url else
flash[ :error ] = "Don't be cheeky! Mind your own assets!" redirect_to root_url end
end
|
Выше мы добавили строку для назначения asset
переменной, если пользователь имеет общий доступ к папке этого ресурса (файла).
Ограничение действий, доступных для общих пользователей
Нам нужно ограничить доступ к верхним кнопкам: «Загрузить» и «Новая папка». Откройте app/views/home/index.html.erb
файл и измените top_menu
список ul следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
<% unless @is_this_folder_being_shared %> < ul id = "top_menu" > <% if @current_folder %> < li ><%= link_to "Upload", new_file_path(@current_folder) %></ li > < li ><%= link_to "New Folder", new_sub_folder_path(@current_folder) %></ li > <% else %>
< li ><%= link_to "Upload", new_asset_path %></ li > < li ><%= link_to "New Folder", new_folder_path %></ li > <% end %>
</ul>
<% else %>
< h3 >This folder is being shared to you by <%= @current_folder.user.name %></ h3 > <% end %>
|
Мы используем @is_this_folder_being_shared
переменную, чтобы определить, является ли current_folder действительно папкой, доступной другим пользователям, или нет. Если это так, мы их спрячем и покажем сообщение.
На этой же странице рядом со @folders
списком настройте действия, как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
|
< div class = "actions" > < div class = "share" > <%= link_to "Share", "#", :folder_id => folder.id, :folder_name => folder.name unless @is_this_folder_being_shared%> </div>
< div class = "rename" > <%= link_to "Rename", rename_folder_path(folder) unless @is_this_folder_being_shared%> </div>
< div class = "delete" > <%= link_to "Delete", folder, :confirm => 'Are you sure to delete the folder and all of its contents?', :method => :delete unless @is_this_folder_being_shared%> </div>
</div>
|
Этот код ограничивает действия над подпапками папки, доступной другим пользователям.
Далее по Delete
действию файла добавьте:
1
2
3
|
< div class = "delete" > <%= link_to "Delete", asset, :confirm => 'Are you sure?', :method => :delete unless @is_this_folder_being_shared%> </div>
|
Это гарантирует, что действия теперь хорошо защищены для общих пользователей.
Вывод
Несмотря на то, что есть ряд дополнительных вещей, которые нужно превратить в это приложение для полноценного веб-сайта для обмена файлами, это обеспечит надежное начало.
Мы реализовали каждую важную функцию типичного общего доступа к файлам, включая создание (вложенных) папок и использование электронных писем для приглашений.
С точки зрения техник, которые мы использовали в этом руководстве, мы рассмотрели несколько тем: от загрузки файлов со скрепкой до использования пользовательского интерфейса jQuery для модального диалогового окна и отправки запросов на публикацию с помощью AJAX.
This was a massive tutorial; so, take your time, read it again, work along with each step, and you’ll be finished in no time!