Статьи

Создайте Dropbox-подобный файлообменный сайт с Ruby on Rails

В этом руководстве Tuts + Premium мы узнаем, как создать веб-приложение для обмена файлами, например Dropbox , с использованием Ruby on Rails.


Огромный успех DropBox ясно показывает, что существует огромная потребность в простом и быстром обмене файлами.

Наше приложение будет иметь следующие функции:

  • простая аутентификация пользователя
  • загружать файлы и сохранять их в Amazon S3
  • создавать папки и организовывать
  • делиться папками с другими пользователями

В этом уроке я укажу различные способы улучшения нашего приложения. Попутно мы рассмотрим различные концепции, в том числе Rails и AJAX.


«Убедитесь, что вы обновились до Rails 3, чтобы следовать этому уроку».

Прежде чем начать, убедитесь, что на вашем компьютере установлены все следующие компоненты:

  • Ruby 1.9. +
  • Рельсы 3+
  • MySQL 5

Если вы новичок в Ruby и Rails, вы можете начать процесс обучения здесь . Мы будем использовать терминал (Mac) или командную строку, если вы пользователь Windows.


Во-первых, нам нужно создать приложение 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 по умолчанию следующим образом:



Далее мы создадим базовую аутентификацию пользователя.

Мы будем использовать фантастический камень разработки, чтобы помочь с нашей аутентификацией пользователя. Мы также можем использовать отличный генератор Райана, чтобы помочь с нашим макетом и просмотреть помощников.

Добавьте эти два камня в свой 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», указав свои учетные данные:


Теперь у нас есть базовая аутентификация. Следующим шагом будет приведение в порядок макета и внешнего вида страниц вместе со ссылками.


«Файл макета 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;
}

Если вы обновите домашнюю страницу в своем браузере, вы увидите:



Мы будем использовать драгоценный камень 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 здесь .


«Теперь мы рассмотрим, как хранить файлы на 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, перед тем как передать его пользователю, перед загрузкой файла может возникнуть значительная задержка. Но для простоты мы оставим все как есть.

На следующем шаге мы правильно перечислим файлы на домашней странице.


Давайте красиво перечислим файлы на домашней странице. Мы покажем файлы, когда пользователь перейдет к 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>

На следующем шаге мы узнаем, как создавать папки.


Нам нужно организовать наши файлы и папки. Приложение должно предоставлять возможность пользователям создавать структуры папок и загружать в них файлы.

Мы можем создавать папки практически в представлениях, используя таблицу базы данных (модель), которая называется Папка. На самом деле мы не будем создавать папки в файловой системе или на 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 . Далее мы разместим эти папки на домашней странице.


Чтобы отобразить папки на домашней странице, нам нужно загрузить папки в переменной экземпляра из контроллера. Идите и откройте, 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>

Большой!То, что у нас здесь, выглядит хорошо. Далее мы узнаем, как создавать вложенные папки и отображать хлебные крошки.


На этом шаге мы позволим пользователям создавать папки внутри других папок, используя 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».

«дом / панировочные сухари»%>

Обратите внимание, что вы должны передать «домашний» каталог для вызова частичного из каталога «папки», потому что они оба находятся в одном каталоге. Давайте посмотрим некоторые скриншоты хлебных крошек в действии!






Эта навигация по крошкам должна быть вполне подходящей для наших нужд. Далее мы добавим возможность загружать и хранить файлы в папке.


Аналогично созданию подпапки, мы будем использовать тот же тип маршрута для загрузки (под) файла в папку. Откройте 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

Этот код гарантирует, что пользователь будет перенаправлен обратно в нужную папку после завершения загрузки.

Теперь вы сможете загружать файлы в определенные папки. Дайте ему попытку убедиться, что все работает, прежде чем двигаться вперед.



Теперь, когда у нас есть все файлы и папки, мы должны иметь возможность редактировать, удалять, загружать и делиться ими. Давайте начнем с создания некоторых ссылок на главной странице.

«Нам нужно , чтобы позволить пользователям сделать три вещи в папки: 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 %>

Страница должна теперь выглядеть как на следующем изображении, когда вы нажимаете, чтобы переименовать папку.



«В этом приложении 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.


Теперь мы рассмотрим, как отправлять электронные письма, когда пользователь делится папкой с другими.

«Отправлять электронную почту в 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
  default :from => "[email protected]"
    
  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

Теперь, если вы поделитесь папкой, она отправит электронное письмо общему пользователю. После того, как вы нажмете кнопку «Поделиться» в диалоговом окне, вам, возможно, придется подождать несколько секунд, чтобы система отправила электронное письмо пользователю, прежде чем вы увидите флэш-сообщение.



Пока что, хотя пользователь может поделиться своей папкой с другими, они еще не увидят его на своих страницах 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!