Create attachment model and hook it up to todo

An attachment has write permissions on group so that managing
attachments work from different users in same group, i.e. user apache
and user mail.
This commit is contained in:
Reinier Balt 2015-08-05 13:01:02 +02:00
parent 5499ac2a03
commit 2bd68fecb7
10 changed files with 121 additions and 39 deletions

1
.gitignore vendored
View file

@ -15,6 +15,7 @@
/db/*.db
/db/*.sqlite3
/db/*.sqlite3-journal
/db/assets/*
/log/*.log
/public/assets/
/tmp

20
app/models/attachment.rb Normal file
View file

@ -0,0 +1,20 @@
class Attachment < ActiveRecord::Base
belongs_to :todo, touch: true
has_attached_file :file,
url: '/:class/:id/:basename.:extension',
path: ":rails_root/db/assets/#{Rails.env}/:class/:id/:basename.:extension",
override_file_permissions: 0660
do_not_validate_attachment_file_type :file
# validates_attachment_content_type :file, :content_type => ["text/plain"]
before_destroy :delete_attached_file
private
def delete_attached_file
file = nil
save!
end
end

View file

@ -24,6 +24,7 @@ class Todo < ActiveRecord::Base
:source => :predecessor
has_many :pending_successors, -> {where('todos.state = ?', 'pending')}, :through => :predecessor_dependencies,
:source => :successor
has_many :attachments, dependent: :destroy
# scopes for states of this todo
scope :active, -> { where state: 'active' }

View file

@ -96,6 +96,7 @@ class User < ActiveRecord::Base
has_many :notes, -> { order "created_at DESC" }, dependent: :delete_all
has_one :preference, dependent: :destroy
has_many :attachments, through: :todos
validates_presence_of :login
validates_presence_of :password, if: :password_required?

View file

@ -101,11 +101,12 @@ Rails.application.routes.draw do
end
end
# match /todos/tag and put everything in :name, including extensions like .m and .txt.
# match /todos/tag and put everything in :name, including extensions like .m and .txt.
# This means the controller action needs to parse the extension and set format/content type
# Needed for /todos/tag/first.last.m to work
get 'todos/tag/:name' => 'todos#tag', :as => :tag, :format => false, :name => /.*/
get 'attachments/:id/:filename' => "todos#attachment"
get 'tags.autocomplete' => "todos#tags", :format => 'autocomplete'
get 'todos/done/tag/:name' => "todos#done_tag", :as => :done_tag
get 'todos/all_done/tag/:name' => "todos#all_done_tag", :as => :all_done_tag

View file

@ -0,0 +1,9 @@
class CreateAttachments < ActiveRecord::Migration
def change
create_table :attachments do |t|
t.references :todo, index: true
t.attachment :file
t.timestamps
end
end
end

View file

@ -11,11 +11,23 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150209233951) do
ActiveRecord::Schema.define(version: 20150805144100) do
create_table "attachments", force: true do |t|
t.integer "todo_id"
t.string "file_file_name"
t.string "file_content_type"
t.integer "file_file_size"
t.datetime "file_updated_at"
t.datetime "created_at"
t.datetime "updated_at"
end
add_index "attachments", ["todo_id"], name: "index_attachments_on_todo_id", using: :btree
create_table "contexts", force: true do |t|
t.string "name", null: false
t.integer "position"
t.integer "position", default: 0
t.integer "user_id", default: 1
t.datetime "created_at"
t.datetime "updated_at"
@ -85,11 +97,11 @@ ActiveRecord::Schema.define(version: 20150209233951) do
add_index "preferences", ["user_id"], name: "index_preferences_on_user_id", using: :btree
create_table "projects", force: true do |t|
t.string "name", null: false
t.integer "position"
t.integer "user_id", default: 1
t.text "description"
t.string "state", limit: 20, null: false
t.string "name", null: false
t.integer "position", default: 0
t.integer "user_id", default: 1
t.text "description", limit: 16777215
t.string "state", limit: 20, null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "default_context_id"
@ -104,17 +116,17 @@ ActiveRecord::Schema.define(version: 20150209233951) do
add_index "projects", ["user_id"], name: "index_projects_on_user_id", using: :btree
create_table "recurring_todos", force: true do |t|
t.integer "user_id", default: 1
t.integer "context_id", null: false
t.integer "user_id", default: 1
t.integer "context_id", null: false
t.integer "project_id"
t.string "description", null: false
t.text "notes"
t.string "state", limit: 20, null: false
t.string "description", null: false
t.text "notes", limit: 16777215
t.string "state", limit: 20, null: false
t.datetime "start_from"
t.string "ends_on"
t.datetime "end_date"
t.integer "number_of_occurrences"
t.integer "occurrences_count", default: 0
t.integer "occurrences_count", default: 0
t.string "target"
t.integer "show_from_delta"
t.string "recurring_period"
@ -123,7 +135,7 @@ ActiveRecord::Schema.define(version: 20150209233951) do
t.integer "every_other2"
t.integer "every_other3"
t.string "every_day"
t.boolean "only_work_days", default: false
t.boolean "only_work_days", default: false
t.integer "every_count"
t.integer "weekday"
t.datetime "completed_at"
@ -141,7 +153,7 @@ ActiveRecord::Schema.define(version: 20150209233951) do
t.datetime "updated_at"
end
add_index "sessions", ["session_id"], name: "index_sessions_on_session_id", using: :btree
add_index "sessions", ["session_id"], name: "sessions_session_id_index", using: :btree
create_table "taggings", force: true do |t|
t.integer "taggable_id"
@ -162,19 +174,19 @@ ActiveRecord::Schema.define(version: 20150209233951) do
add_index "tags", ["name"], name: "index_tags_on_name", using: :btree
create_table "todos", force: true do |t|
t.integer "context_id", null: false
t.integer "context_id", null: false
t.integer "project_id"
t.string "description", null: false
t.text "notes"
t.string "description", null: false
t.text "notes", limit: 16777215
t.datetime "created_at"
t.datetime "due"
t.datetime "completed_at"
t.integer "user_id", default: 1
t.integer "user_id", default: 1
t.datetime "show_from"
t.string "state", limit: 20, null: false
t.string "state", limit: 20, null: false
t.integer "recurring_todo_id"
t.datetime "updated_at"
t.text "rendered_notes"
t.text "rendered_notes", limit: 16777215
end
add_index "todos", ["context_id"], name: "index_todos_on_context_id", using: :btree
@ -212,7 +224,7 @@ ActiveRecord::Schema.define(version: 20150209233951) do
create_table "users", force: true do |t|
t.string "login", limit: 80, null: false
t.string "crypted_password", limit: 60, null: false
t.string "crypted_password", limit: 60
t.string "token"
t.boolean "is_admin", default: false, null: false
t.string "first_name"

7
test/fixtures/attachments.yml vendored Normal file
View file

@ -0,0 +1,7 @@
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
one:
todo_id:
two:
todo_id:

View file

@ -0,0 +1,7 @@
require 'test_helper'
class AttachmentTest < ActiveSupport::TestCase
# test "the truth" do
# assert true
# end
end

View file

@ -72,26 +72,26 @@ class TodoTest < ActiveSupport::TestCase
def test_validate_show_from_must_be_a_date_in_the_future
t = @not_completed2
t.show_from = 1.week.ago
assert !t.save, "todo should not be saved without validation errors"
assert_equal 1, t.errors.count
assert_equal "must be a date in the future", t.errors[:show_from][0]
end
def test_validate_circular_dependencies
@completed.activate!
@not_completed3=@completed
# 2 -> 1
@not_completed1.add_predecessor(@not_completed2)
assert @not_completed1.save!
assert_equal 1, @not_completed2.successors.count
# 3 -> 2 -> 1
@not_completed2.add_predecessor(@not_completed3)
assert @not_completed2.save!
assert_equal 1, @not_completed3.successors.count
# 1 -> 3 -> 2 -> 1 == circle
assert_raises ActiveRecord::RecordInvalid do
@not_completed3.add_predecessor(@not_completed1)
@ -131,7 +131,7 @@ class TodoTest < ActiveSupport::TestCase
t.toggle_completion!
assert_equal :active, t.aasm.current_state
end
def test_toggle_completion_with_show_from_in_future
t = @not_completed1
t.show_from= 1.week.from_now
@ -140,12 +140,12 @@ class TodoTest < ActiveSupport::TestCase
t.toggle_completion!
assert_equal :completed, t.aasm.current_state
end
def test_toggle_completion_with_show_from_in_past
t = @not_completed1
t.update_attribute(:show_from, 1.week.ago)
assert_equal :active, t.aasm.current_state
assert t.toggle_completion!, "shoud be able to mark active todo complete even if show_from is set in the past"
assert_equal :completed, t.aasm.current_state
end
@ -219,7 +219,7 @@ class TodoTest < ActiveSupport::TestCase
# And I update the state of the todo from its project
new_todo.update_state_from_project
# Then the todo should be hidden
assert new_todo.hidden?
assert new_todo.hidden?
end
def test_initial_state_defaults_to_active
@ -280,7 +280,7 @@ class TodoTest < ActiveSupport::TestCase
assert todo.pending?, "todo with predecessor should be blocked"
# cannot activate if part of hidden project
assert_raise(AASM::InvalidTransition) { todo.activate! }
assert_raise(AASM::InvalidTransition) { todo.activate! }
todo.remove_predecessor(todo2)
assert todo.reload.hidden?, "todo should be put back in hidden state"
@ -337,7 +337,7 @@ class TodoTest < ActiveSupport::TestCase
@not_completed1.add_predecessor(@not_completed2)
@not_completed1.save_predecessors
# blocking is not done automagically
@not_completed1.block!
@not_completed1.block!
assert @not_completed1.uncompleted_predecessors?
assert @not_completed1.pending?, "a todo with predecessors should be pending"
@ -358,7 +358,7 @@ class TodoTest < ActiveSupport::TestCase
@not_completed1.add_predecessor_list("#{@not_completed2.id}, #{@not_completed3.id}")
@not_completed1.save_predecessors
# blocking is not done automagically
@not_completed1.block!
@not_completed1.block!
# Then @completed1 should have predecessors and should be blocked
assert @not_completed1.uncompleted_predecessors?
@ -526,20 +526,43 @@ class TodoTest < ActiveSupport::TestCase
assert !older_created_todos.include?(todo_now)
assert !recent_created_todos.include?(todo_old)
end
def test_notes_are_rendered_on_save
user = @completed.user
todo = user.todos.create(:description => "test", :context => @completed.context)
assert_nil todo.notes
assert_nil todo.rendered_notes
todo.notes = "*test*"
todo.save!
todo.reload
assert_equal "*test*", todo.notes
assert_equal "<p><strong>test</strong></p>", todo.rendered_notes
end
def test_attachments_are_removed_after_delete
# Given a user and a todo withou any attachments
todo = @not_completed1
assert_equal 0, todo.attachments.count, "we start without attachments"
assert_equal 0, todo.user.attachments.count, "the user has no attachments"
# When I add a file as attachment to a todo of this user
attachment = todo.attachments.build
attachment.file = File.open(File.join(Rails.root, 'test', 'fixtures', 'email_with_multipart.txt'))
attachment.save!
new_path = attachment.file.path
# then the attachment should be there
assert File.exists?(new_path), "attachment should be on file system"
assert_equal 1, todo.attachments.reload.count, "should have one attachment"
# When I destroy the todo
todo.destroy!
# Then the attachement and file should nogt be there anymore
assert_equal 0, todo.user.attachments.reload.count
assert !File.exists?(new_path), "attachment should not be on file system"
end
end