diff --git a/app/assets/stylesheets/carbuncle.css b/app/assets/stylesheets/carbuncle.css
index 0fc4080..ffc980f 100644
--- a/app/assets/stylesheets/carbuncle.css
+++ b/app/assets/stylesheets/carbuncle.css
@@ -105,4 +105,8 @@ div div.elem:last-of-type
.submit_field{
padding-top: 15px;
+}
+
+.clickable {
+ cursor: pointer;
}
\ No newline at end of file
diff --git a/app/controllers/administration/manage_users_controller.rb b/app/controllers/administration/manage_users_controller.rb
index 1b6f685..a4af5fe 100644
--- a/app/controllers/administration/manage_users_controller.rb
+++ b/app/controllers/administration/manage_users_controller.rb
@@ -43,7 +43,7 @@ def destroy
@user.destroy
end
- redirect_to administration_users_path
+ redirect_to administration_manage_users_path
end
private
@@ -63,7 +63,7 @@ def save_form
@user.attributes = user_params
if @user.save
flash[:notice] = "Successfully saved #{@user.email}"
- redirect_to administration_users_url
+ redirect_to administration_manage_users_url
else
setup_form
end
diff --git a/app/controllers/notes_controller.rb b/app/controllers/notes_controller.rb
new file mode 100644
index 0000000..8799316
--- /dev/null
+++ b/app/controllers/notes_controller.rb
@@ -0,0 +1,91 @@
+class NotesController < ApplicationController
+ before_action :setup_sorting_variables, only: [:index]
+ before_action :find_note, except: [:index]
+
+ def index
+ sort_key = [:value, :email, :created, :updated][@sort]
+ direction = (@asc == 1) ? :asc : :desc
+
+ @notes = Note.similar_notes(params[:search]).
+ ordered_by(sort_key, direction).page(@page).per_page(10)
+ end
+
+ def search_users
+ @users = User.similar_emails(params[:partial_email]).
+ ordered_by(:email, :asc).limit(10)
+
+ respond_to do |format|
+ format.html { render partial: "/layouts/user_list" }
+ format.json { render :search_users }
+ end
+ end
+
+ def show
+ end
+
+ def new
+ setup_form
+ end
+
+ def edit
+ setup_form
+ end
+
+ def create
+ save_form
+ end
+
+ def update
+ save_form
+ end
+
+ def destroy
+ if @note.nil?
+ flash[:error] = "Can't find note"
+ elsif @note.new_record?
+ flash[:error] = "Can't destroy new note"
+ else
+ flash[:notice] = "You destroyed #{@note.value}"
+ @note.destroy
+ end
+
+ redirect_to notes_path
+ end
+
+ private
+ def find_note
+ @note = if params[:id].blank?
+ Note.new
+ else
+ Note.where(id: params[:id]).first
+ end
+ end
+
+ def setup_form
+ render :form
+ end
+
+ def save_form
+ @note.attributes = note_params
+ if @note.save
+ flash[:notice] = "Successfully saved #{@note.value}"
+ redirect_to notes_url
+ else
+ setup_form
+ end
+ end
+
+ def note_params
+ params.require(:note).permit(:value, :user_id)
+ end
+
+ def view_content
+ render params[:action]
+ end
+
+ def setup_sorting_variables
+ @sort = params[:sort].blank? ? 0 : params[:sort].to_i
+ @asc = params[:asc].blank? ? 0 : params[:asc].to_i
+ @page = params[:page].blank? ? 1 : params[:page].to_i
+ end
+end
diff --git a/app/controllers/products_controller.rb b/app/controllers/products_controller.rb
index 393047d..5b5000f 100644
--- a/app/controllers/products_controller.rb
+++ b/app/controllers/products_controller.rb
@@ -3,7 +3,7 @@ class ProductsController < ApplicationController
before_action :find_product, except: [:index]
def index
- sort_key = [:name, :descritpion, :cost, :created, :updated][@sort]
+ sort_key = [:name, :description, :cost, :created, :updated][@sort]
direction = (@asc == 1) ? :asc : :desc
@products = Product.similar_names(params[:search]).
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index c369bd5..30fa72a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,7 +1,7 @@
module ApplicationHelper
def sortable(column, title = nil)
title ||= column.titleize
- css_class = (column == @sort) ? "current ↵ #{(@asc == 1) ? 'asc' : 'desc'}" : nil
+ css_class = (column == @sort) ? "current #{(@asc == 1) ? 'asc' : 'desc'}" : nil
direction = (column == @sort && @asc == 1) ? 0 : 1
#link_to title, {:sort => column, :direction => direction}, {:class => css_class}
link_to title, params.merge(sort: column, asc: direction, page: nil), {:class => css_class}
diff --git a/app/models/note.rb b/app/models/note.rb
new file mode 100644
index 0000000..4be831e
--- /dev/null
+++ b/app/models/note.rb
@@ -0,0 +1,22 @@
+class Note < ActiveRecord::Base
+ belongs_to :user
+
+ validates :value, presence: true
+
+ scope :similar_notes, -> (partial) { partial.blank? ? all : where{ value =~ "%#{partial}%" } }
+
+ def self.ordered_by(sort, asc)
+ case sort
+ when :value
+ order{ value.send(asc) }
+ when :email
+ joins{ user.outer }.order{ user.email.send(asc) }
+ when :created
+ order{ created_at.send(asc) }
+ when :updated
+ order{ updated_at.send(asc) }
+ else
+ all
+ end
+ end
+end
diff --git a/app/models/painting.rb b/app/models/painting.rb
index 985702c..6644872 100644
--- a/app/models/painting.rb
+++ b/app/models/painting.rb
@@ -4,10 +4,10 @@ class Painting < ActiveRecord::Base
mount_uploader :image, ImageUploader
- before_create :default_title
+ before_validation :default_title
def default_title
- self.title ||= File.basename(image.filename, '.*').titleize if image
+ self.title ||= File.basename(image.filename.to_s, '.*').titleize if image
end
def self.ordered_by(sort, asc)
diff --git a/app/models/user.rb b/app/models/user.rb
index 3e108eb..9516456 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,4 +1,5 @@
class User < ActiveRecord::Base
+ has_many :notes, dependent: :destroy
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable, :omniauthable,
diff --git a/app/presenters/note_presenter.rb b/app/presenters/note_presenter.rb
new file mode 100644
index 0000000..b09a451
--- /dev/null
+++ b/app/presenters/note_presenter.rb
@@ -0,0 +1,12 @@
+class NotePresenter < BasePresenter
+ presents :note
+
+ delegate :value, :user, :created_at, :updated_at, to: :note
+
+ def creator
+ unless note.user.nil?
+ note.user.email
+ end
+ end
+ # Other presenter methods to help show user information
+end
diff --git a/app/views/administration/manage_users/index.html.haml b/app/views/administration/manage_users/index.html.haml
index 28feb30..8949746 100644
--- a/app/views/administration/manage_users/index.html.haml
+++ b/app/views/administration/manage_users/index.html.haml
@@ -17,27 +17,8 @@
- else
No users match your search criteria
-
%br/
%br/
.white_link.small_margin
= link_to "New User", new_administration_manage_user_path, class: 'btn btn-default'
-
-
-:javascript
- // Add click event to each of the headers of the products table, and pagination
- $(function () {
- $('#users_table th a, #users_table .pagination a').on('click', ↵
- function () {
- $.getScript(this.href);
- return false;
- }
- );
-
- // Search form.
- $('#user_search').submit(function () {
- $.get(this.action, $(this).serialize(), null, 'script');
- return false;
- });
- });
\ No newline at end of file
diff --git a/app/views/layouts/_user_list.html.haml b/app/views/layouts/_user_list.html.haml
new file mode 100644
index 0000000..9b81656
--- /dev/null
+++ b/app/views/layouts/_user_list.html.haml
@@ -0,0 +1,6 @@
+- unless @users.empty?
+ .elem
+ Select from users with similar emails
+ - @users.each do |u|
+ .selection{ id: u.id }
+ = u.email
\ No newline at end of file
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 38ed3a1..d070a27 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -12,13 +12,15 @@
- <%= link_to "Dashboard", root_path, {class: 'btn btn-primary'} %>
- <%= link_to "Users", administration_manage_users_path, {class: 'btn btn-primary'} %>
- <%= link_to "Images", paintings_path, {class: 'btn btn-primary'} %>
- <%= link_to "Image Fader", fader_path, {class: 'btn btn-primary'} %>
- <%= link_to "Products", products_path, {class: 'btn btn-primary'} %>
- <%= link_to "Knockout test", knockout_test_path, {class: 'btn btn-primary'} %>
-
+
<% if user_signed_in? %>
@@ -34,6 +36,12 @@
+ <% unless flash[:alert].blank? %>
+
+ <%= flash[:alert] %>
+
+ <% end %>
+
<% unless flash[:notice].blank? %>
<%= flash[:notice] %>
diff --git a/app/views/notes/_list.html.haml b/app/views/notes/_list.html.haml
new file mode 100644
index 0000000..d222089
--- /dev/null
+++ b/app/views/notes/_list.html.haml
@@ -0,0 +1,14 @@
+= will_paginate @notes
+
+= hidden_field_tag :sort, params[:sort]
+= hidden_field_tag :page, params[:page]
+
+%table.table.table-striped
+ %tr
+ - ["Value", "User", "Created", "Modified"].each_with_index do |title, index|
+ %th= sortable index, title
+ %th
+ %th
+ %th
+
+ = render partial: 'notes_table'
diff --git a/app/views/notes/_notes_table.html.haml b/app/views/notes/_notes_table.html.haml
new file mode 100644
index 0000000..559eb2a
--- /dev/null
+++ b/app/views/notes/_notes_table.html.haml
@@ -0,0 +1,10 @@
+- @notes.each do |note|
+ - present note do |n|
+ %tr
+ %td= n.value
+ %td= n.creator || "No user"
+ %td= n.created_at.strftime('%D')
+ %td= n.updated_at.strftime('%D')
+ %td= link_to "Show", n.note
+ %td= link_to 'Edit', edit_note_path(n.note)
+ %td= link_to 'Destroy', n.note, method: :delete, data: { confirm: "Are you sure?" }
\ No newline at end of file
diff --git a/app/views/notes/form.html.haml b/app/views/notes/form.html.haml
new file mode 100644
index 0000000..155eecf
--- /dev/null
+++ b/app/views/notes/form.html.haml
@@ -0,0 +1,89 @@
+%h1= (@note.new_record?) ? 'Create note' : 'Edit note'
+
+- if @note.errors.any?
+ #error_explanation
+ %h2= pluralize(@note.errors.count, t(:error)).html_safe + t(:_prohibited_this_note_from_being_saved)
+ %ul
+ - @note.errors.full_messages.each do |msg|
+ %li= msg
+
+.elem.generic_form
+ = form_for @note, as: :note, url: "/notes/#{@note.id}" do |f|
+ .elem
+ .labels
+ = f.label :value
+ = f.text_field :value, class: "form-control"
+
+ = f.hidden_field :user_id, "data-bind" => "value: userId"
+
+ .elem
+ .labels
+ = label_tag :user_email
+ = text_field_tag :user_email, f.object.user.nil? ? "" : f.object.user.email, "data-bind" => "value: email, valueUpdate: 'afterkeydown'", class: "form-control"
+
+ .results.well{ "data-bind" => "visible: showUserResults" }
+ %h5
+ Please select from the list below (If the user is not found, then no user will be set)
+ %span{ "data-bind" => "template: { name: 'users', foreach: emailList }" }
+
+ %script{ "id" => "users", "type" => "text/html" }
+ .clickable{ "data-bind" => "text: email, click: $parent.selectUser" }
+
+ .btn-group.submit_field
+ - if @note.new_record?
+ = f.submit 'Create', class: 'btn btn-success'
+ - else
+ = f.submit 'Update', class: 'btn btn-success'
+ = link_to 'Show', @note, { class: 'btn btn-default' }
+
+ = link_to 'Back', notes_path, { class: 'btn btn-default' }
+
+:javascript
+
+ var initial_user = $('#user_email').val(),
+ initial_user_id = $('#note_user_id').val(),
+ viewModel = { email: ko.observable(initial_user || ''),
+ userId: ko.observable(initial_user_id || ''),
+ showUserResults: ko.observable(false),
+ emailList: ko.observableArray() };
+
+ viewModel.email.subscribe(function (partial) {
+ if (partial === '') {
+ viewModel.userId('');
+ } else {
+ var posting = {partial_email: ko.unwrap(partial)};
+ $.ajax({
+ url: '#{search_users_path}',
+ dataType: 'json',
+ method: 'post',
+ data: posting,
+ success: function (results) {
+ // If the only result is the partial, then hide results
+ if (results.length === 1 && results[0].email === partial) {
+ viewModel.showUserResults(false);
+ } else {
+ viewModel.showUserResults(true);
+ }
+ viewModel.emailList(results);
+ },
+ error: function (jqXHR, textStatus, errorThrown) {
+ alert(jqXHR.responseText);
+ }
+ });
+ }
+ });
+
+ viewModel.selectUser = function (data) {
+ viewModel.email(data.email);
+ };
+
+ //Before form submits check if the user id can be updated
+ $('#edit_note').submit(function () {
+ if ($('.results .clickable:hidden').length === 1) {
+ if (viewModel.emailList()[0].email === viewModel.email()) {
+ viewModel.userId(viewModel.emailList()[0].id);
+ }
+ }
+ });
+
+ ko.applyBindings(viewModel);
\ No newline at end of file
diff --git a/app/views/notes/index.html.haml b/app/views/notes/index.html.haml
new file mode 100644
index 0000000..6686bec
--- /dev/null
+++ b/app/views/notes/index.html.haml
@@ -0,0 +1,25 @@
+%h1 Listing Notes
+
+%br/
+
+.elem
+ = form_tag notes_path, method: :get, id: "note_search" do
+ = text_field_tag :search, params[:search], placeholder: "Search notes"
+ = submit_tag "Search", name: nil
+
+- unless @notes.empty?
+ .elem{ id: 'notes_table' }
+ = render partial: 'list'
+- else
+ .elem
+ - if params[:search].blank?
+ No notes exist
+ - else
+ No notes match your search criteria
+
+
+%br/
+%br/
+
+.white_link.small_margin
+ = link_to "New note", new_note_path, class: 'btn btn-default'
diff --git a/app/views/notes/index.json.jbuilder b/app/views/notes/index.json.jbuilder
new file mode 100644
index 0000000..a7f871c
--- /dev/null
+++ b/app/views/notes/index.json.jbuilder
@@ -0,0 +1,4 @@
+json.array!(@notes) do |note|
+ json.extract! note, :value, :user_id
+ json.url note_url(note, format: :json)
+end
diff --git a/app/views/notes/search_users.json.jbuilder b/app/views/notes/search_users.json.jbuilder
new file mode 100644
index 0000000..d1d43a3
--- /dev/null
+++ b/app/views/notes/search_users.json.jbuilder
@@ -0,0 +1,3 @@
+json.array!(@users) do |user|
+ json.extract! user, :id, :email
+end
diff --git a/app/views/notes/show.html.haml b/app/views/notes/show.html.haml
new file mode 100644
index 0000000..4365de7
--- /dev/null
+++ b/app/views/notes/show.html.haml
@@ -0,0 +1,13 @@
+.elem.small_margin.btn-group
+ = link_to 'Edit', edit_note_path(@note), { class: 'btn btn-default' }
+ = link_to 'Back', notes_path, { class: 'btn btn-default' }
+
+.elem
+ .label
+ Note:
+ = @note.value
+- unless @note.user.nil?
+ .elem
+ .label
+ User:
+ = @note.user.email
\ No newline at end of file
diff --git a/app/views/notes/show.json.jbuilder b/app/views/notes/show.json.jbuilder
new file mode 100644
index 0000000..d98bdeb
--- /dev/null
+++ b/app/views/notes/show.json.jbuilder
@@ -0,0 +1 @@
+json.extract! @note, :value, :user_id, :created_at, :updated_at
diff --git a/app/views/products/index.html.haml b/app/views/products/index.html.haml
index adc8147..a0722c9 100644
--- a/app/views/products/index.html.haml
+++ b/app/views/products/index.html.haml
@@ -23,21 +23,3 @@
.white_link.small_margin
= link_to "New Product", new_product_path, class: 'btn btn-default'
-
-
-:javascript
- // Add click event to each of the headers of the products table, and pagination
- $(function () {
- $('#products_table th a, #products_table .pagination a').on('click', ↵
- function () {
- $.getScript(this.href);
- return false;
- }
- );
-
- // Search form.
- $('#product_search').submit(function () {
- $.get(this.action, $(this).serialize(), null, 'script');
- return false;
- });
- });
\ No newline at end of file
diff --git a/config/environments/development.rb b/config/environments/development.rb
index fbd2468..90e0c6d 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -13,6 +13,8 @@
config.consider_all_requests_local = true
config.action_controller.perform_caching = false
+ config.action_mailer.default_url_options = { :host => "http://localhost:3000" }
+
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = false
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 848d9a6..3667272 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -10,7 +10,7 @@
# Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class
# with default "from" parameter.
- config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
+ config.mailer_sender = 'adamdboudreau@hotmail.co.uk'
# Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer'
diff --git a/config/routes.rb b/config/routes.rb
index ab0efd8..de54fe4 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,4 +1,7 @@
Carbuncle::Application.routes.draw do
+ resources :notes
+ post 'search_users' => 'notes#search_users'
+
resources :products
get 'fader' => 'paintings#fader'
delete 'image_batch_destroy' => 'paintings#batch_destroy'
diff --git a/db/development.sqlite3 b/db/development.sqlite3
index e180fc6..01e83d3 100644
Binary files a/db/development.sqlite3 and b/db/development.sqlite3 differ
diff --git a/db/migrate/20140911002456_create_notes.rb b/db/migrate/20140911002456_create_notes.rb
new file mode 100644
index 0000000..1fb8d0f
--- /dev/null
+++ b/db/migrate/20140911002456_create_notes.rb
@@ -0,0 +1,10 @@
+class CreateNotes < ActiveRecord::Migration
+ def change
+ create_table :notes do |t|
+ t.string :value
+ t.integer :user_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index fba4d5e..a3d565f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,14 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20131210005441) do
+ActiveRecord::Schema.define(version: 20140911002456) do
+
+ create_table "notes", force: true do |t|
+ t.string "value"
+ t.integer "user_id"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
create_table "paintings", force: true do |t|
t.string "image"
diff --git a/db/test.sqlite3 b/db/test.sqlite3
index 969ce1d..b56b46d 100644
Binary files a/db/test.sqlite3 and b/db/test.sqlite3 differ
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
new file mode 100644
index 0000000..068aa65
--- /dev/null
+++ b/spec/factories/notes.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :note do
+ value "Test note"
+ end
+end
\ No newline at end of file
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
new file mode 100644
index 0000000..64cecc9
--- /dev/null
+++ b/spec/models/note_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Note do
+ it { should validate_presence_of :value }
+
+ describe ".similar_notes" do
+ let(:note) { FactoryGirl.create(:note) }
+ it "should return all notes when given nil" do
+ Note.similar_notes(nil).should == [note]
+ end
+
+ it "should return notes with the partial note given" do
+ Note.similar_notes("est").should == [note]
+ end
+
+ it "should not return notes that don't match the partial given" do
+ Note.similar_notes("Some note that is different from the note factory").should be_empty
+ end
+ end
+
+ describe ".ordered_by" do
+ let(:notes) do
+ n1 = n2 = nil
+
+ Timecop.freeze(2008, 7, 1, 12, 0, 0) do
+ n1 = FactoryGirl.create(:note)
+ end
+
+ Timecop.freeze(2008, 7, 1, 12, 30, 0) do
+ n2 = FactoryGirl.create(:note)
+ end
+ n3 = FactoryGirl.create(:note)
+
+ [n1, n2, n3]
+ end
+
+ let(:alpha_notes) do
+ [FactoryGirl.create(:note, value: 'All your base are belong to us!'), FactoryGirl.create(:note, value: 'I FEEL you mon!'), FactoryGirl.create(:note, value: 'Vi sitter i ventrilo och spelar DotA')]
+ end
+
+ context "value" do
+ it "should sort by note values" do
+ Note.ordered_by(:value, :asc).should == alpha_notes
+ end
+ end
+
+ context "created" do
+ it "should sort by created time" do
+ Note.ordered_by(:created, :asc).should == notes
+ end
+ end
+
+ context "updated" do
+ it "should sort by updated time" do
+ Note.ordered_by(:updated, :asc).should == notes
+ end
+ end
+
+ context "email" do
+ let(:u1) { FactoryGirl.create(:user, email: 'acdc@test.com') }
+ let(:u2) { FactoryGirl.create(:user, email: 'zztop@test.com') }
+ before do
+ u1.notes << notes[1]
+ u2.notes << notes[2]
+ end
+
+ it "should sort by users associated with the notes" do
+ Note.ordered_by(:email, :asc).should == notes
+ end
+ end
+ end
+end
\ No newline at end of file