diff --git a/.gitignore b/.gitignore index 5b61ab0..6867b3b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ /log/* !/log/.keep /tmp +models_setup.txt +models_setup.txt~ +public/img/ +.byebug_history diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/Gemfile b/Gemfile index b38d1b2..16ee7fe 100644 --- a/Gemfile +++ b/Gemfile @@ -24,7 +24,7 @@ gem 'jbuilder', '~> 2.0' gem 'sdoc', '~> 0.4.0', group: :doc # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use Unicorn as the app server # gem 'unicorn' @@ -32,9 +32,18 @@ gem 'sdoc', '~> 0.4.0', group: :doc # Use Capistrano for deployment # gem 'capistrano-rails', group: :development + gem 'slim' + + gem 'cancancan' + + gem 'bootstrap-sass' + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug' + gem 'rspec-rails' + gem 'faker' + gem 'factory_girl_rails' end group :development do @@ -44,4 +53,3 @@ group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' end - diff --git a/Gemfile.lock b/Gemfile.lock index b0631ce..3c82d3a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,10 +37,17 @@ GEM thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) arel (6.0.3) + autoprefixer-rails (6.3.6) + execjs + bcrypt (3.1.11) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) + bootstrap-sass (3.3.6) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) builder (3.2.2) byebug (8.2.2) + cancancan (1.13.1) coffee-rails (4.1.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.1.x) @@ -50,8 +57,16 @@ GEM coffee-script-source (1.10.0) concurrent-ruby (1.0.1) debug_inspector (0.0.2) + diff-lcs (1.2.5) erubis (2.7.0) execjs (2.6.0) + factory_girl (4.7.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.7.0) + factory_girl (~> 4.7.0) + railties (>= 3.0.0) + faker (1.6.3) + i18n (~> 0.5) globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) @@ -106,6 +121,23 @@ GEM rake (11.1.2) rdoc (4.2.2) json (~> 1.4) + rspec-core (3.4.4) + rspec-support (~> 3.4.0) + rspec-expectations (3.4.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-mocks (3.4.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.4.0) + rspec-rails (3.4.2) + actionpack (>= 3.0, < 4.3) + activesupport (>= 3.0, < 4.3) + railties (>= 3.0, < 4.3) + rspec-core (~> 3.4.0) + rspec-expectations (~> 3.4.0) + rspec-mocks (~> 3.4.0) + rspec-support (~> 3.4.0) + rspec-support (3.4.1) sass (3.4.22) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) @@ -116,6 +148,9 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + slim (3.0.6) + temple (~> 0.7.3) + tilt (>= 1.3.3, < 2.1) spring (1.6.4) sprockets (3.6.0) concurrent-ruby (~> 1.0) @@ -124,6 +159,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + temple (0.7.6) thor (0.19.1) thread_safe (0.3.5) tilt (2.0.2) @@ -143,18 +179,25 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) + bootstrap-sass byebug + cancancan coffee-rails (~> 4.1.0) + factory_girl_rails + faker jbuilder (~> 2.0) jquery-rails pg (~> 0.15) rails (= 4.2.6) + rspec-rails sass-rails (~> 5.0) sdoc (~> 0.4.0) + slim spring turbolinks uglifier (>= 1.3.0) web-console (~> 2.0) BUNDLED WITH - 1.11.2 + 1.12.2 diff --git a/app/assets/javascripts/comments.coffee b/app/assets/javascripts/comments.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/comments.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/favourites.coffee b/app/assets/javascripts/favourites.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/favourites.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/home.coffee b/app/assets/javascripts/home.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/home.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/posts.coffee b/app/assets/javascripts/posts.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/posts.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/sessions.coffee b/app/assets/javascripts/sessions.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/sessions.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/users.coffee b/app/assets/javascripts/users.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/users.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/bootstrap_and_overrides.scss b/app/assets/stylesheets/bootstrap_and_overrides.scss new file mode 100644 index 0000000..d3e0995 --- /dev/null +++ b/app/assets/stylesheets/bootstrap_and_overrides.scss @@ -0,0 +1,4 @@ +body { padding-top: 70px; } + +@import "bootstrap-sprockets"; +@import "bootstrap" diff --git a/app/assets/stylesheets/comments.scss b/app/assets/stylesheets/comments.scss new file mode 100644 index 0000000..3722c12 --- /dev/null +++ b/app/assets/stylesheets/comments.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the comments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/favourites.scss b/app/assets/stylesheets/favourites.scss new file mode 100644 index 0000000..66cc292 --- /dev/null +++ b/app/assets/stylesheets/favourites.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the favourites controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss new file mode 100644 index 0000000..f0ddc68 --- /dev/null +++ b/app/assets/stylesheets/home.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the home controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss new file mode 100644 index 0000000..1a7e153 --- /dev/null +++ b/app/assets/stylesheets/posts.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000..7bef9cf --- /dev/null +++ b/app/assets/stylesheets/sessions.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the sessions controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 0000000..1efc835 --- /dev/null +++ b/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d83690e..0b8eb52 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,4 +2,20 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + def authenticate_user! + redirect_to new_session_path, notice: "Please sign in!" unless user_signed_in? + end + + def user_signed_in? + session[:user_id].present? + end + helper_method :user_signed_in? + + # Cancancan's ability.rb will use this if it exsists, I think -- IDFS + def current_user + @current_user ||= User.find session[:user_id] if user_signed_in? + end + helper_method :current_user + end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..72597fc --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,70 @@ +class CommentsController < ApplicationController + + before_action :authenticate_user! + before_action(:find_comment, {only: [:show, :edit, :update, :destroy]}) + before_action :authorize_comment, only: [:edit, :update, :destroy] + + + def index + @comments = Comment.all + end + + def new + @comment = Comment.new + end + + def create + @post = Post.find params[:post_id] + @comment = Comment.new(comment_params) + @comment.post = @post + @comment.user = current_user + respond_to do |format| + if @comment.save + flash[:notice] = "Comment Created" + format.html { redirect_to post_path(@post) } + format.js { render :reload } + else + flash[:alert] = "Error! Comment not created" + format.html { render "/posts/show" } + format.js { render :reload } + end + end + end + + def edit + end + + # def update + # if @comment.update comment_params + # redirect_to comment_path(@comment), notice: "Comment updated!" + # else + # render :edit + # end + # end + + def show + end + + def destroy + @post = Post.find params[:post_id] + @comment.destroy + redirect_to post_path(@post), notice: "Your comment was deleted." + end + + + private + + def find_comment + @comment = Comment.find params[:id] + end + + def authorize_comment + flash[:alert] = "You do not have permission to manage this comment" + redirect_to root_path unless can? :manage, @comment + end + + def comment_params + params.require(:comment).permit(:body) + end + +end diff --git a/app/controllers/favourites_controller.rb b/app/controllers/favourites_controller.rb new file mode 100644 index 0000000..cefdad9 --- /dev/null +++ b/app/controllers/favourites_controller.rb @@ -0,0 +1,40 @@ +class FavouritesController < ApplicationController + before_action :authenticate_user! + before_action :find_post, only: [:create, :destroy] + + def create + favourite = Favourite.new + favourite.post = @post + favourite.user = current_user + respond_to do |format| + if favourite.save + format.html { redirect_to post_path(@post), notice: "Thanks for favouriting!" } + format.js { render :modify_favourite } + else + format.html { redirect_to post_path(@post), alert: "Can't favourite! Have you already favourited?" } + format.js { render :modify_favourite } + end + end + end + + def destroy + favourite = current_user.favourites.find params[:id] + favourite.destroy + respond_to do |format| + format.html { redirect_to post_path(@post), notice: "Favourite removed!" } + format.js { render :modify_favourite } + end + end + + def index + @favourites = current_user.favourites.all + end + + + private + + def find_post + @post ||= Post.find params[:post_id] + end + +end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb new file mode 100644 index 0000000..06dbcc3 --- /dev/null +++ b/app/controllers/home_controller.rb @@ -0,0 +1,11 @@ +# Home and static pages go in HomeController +class HomeController < ApplicationController + def home + + end + + def about + + end + +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 0000000..9f6c165 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,64 @@ +class PostsController < ApplicationController + + before_action :authenticate_user!, except: [:index, :show] + before_action(:find_post, {only: [:show, :edit, :update, :destroy]}) + before_action :authorize_post, only: [:edit, :update, :destroy] + + + + def index + @posts = Post.all + end + + def new + @post = Post.new + end + + def create + @post = Post.new(post_params) + @post.user = current_user + + if @post.save + flash[:notice] = "Post Created" + redirect_to post_path(@post) + else + flash[:alert] = "Error! Post not created" + render :new + end + end + + def edit + end + + def update + if @post.update post_params + redirect_to post_path(@post), notice: "Blog Post updated!" + else + render :edit + end + end + + def show + @comment = Comment.new + end + + def destroy + @post.destroy + redirect_to posts_path, notice: "The blog post #{@post.title} was deleted." + end + + private + + def authorize_post + flash[:alert] ="You do not have permission to delete the blog post #{@post.title}" + redirect_to root_path unless can? :manage, @post + end + + def find_post + @post = Post.find params[:id] + end + + def post_params + params.require(:post).permit([:title, :body, :category_id]) + end +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..93d3599 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,21 @@ +class SessionsController < ApplicationController + def new + end + + def create + user = User.find_by_email params[:email] + if user && user.authenticate(params[:password]) + session[:user_id] = user.id + redirect_to root_path, notice: "Signed In!" + else + flash[:alert] = "Wrong Credentials!" + render :new + end + end + + def destroy + session[:user_id] = nil + redirect_to root_path, notice: "Logged out" + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..e8002ce --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,21 @@ +class UsersController < ApplicationController + + def new + @user = User.new + end + + def create + user_params = params.require(:user).permit(:first_name, :last_name, :email, + :password, :password_confirmation) + @user = User.new user_params + if @user.save + # we log the user in by setting the session :user_id to the user's id + # in our database so we can identify the user who is logged in by their id + session[:user_id] = @user.id + redirect_to root_path, notice: "Account created!" + else + render :new + end + end + +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 0000000..0ec9ca5 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,2 @@ +module CommentsHelper +end diff --git a/app/helpers/favourites_helper.rb b/app/helpers/favourites_helper.rb new file mode 100644 index 0000000..907962e --- /dev/null +++ b/app/helpers/favourites_helper.rb @@ -0,0 +1,2 @@ +module FavouritesHelper +end diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb new file mode 100644 index 0000000..23de56a --- /dev/null +++ b/app/helpers/home_helper.rb @@ -0,0 +1,2 @@ +module HomeHelper +end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb new file mode 100644 index 0000000..7fefcf4 --- /dev/null +++ b/app/helpers/posts_helper.rb @@ -0,0 +1,5 @@ +module PostsHelper + def user_favourite + @user_favourite ||= @post.favourite_for(current_user) + end +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..8d60438 --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,51 @@ +class Ability + include CanCan::Ability + + def initialize(user) + + user ||= User.new # guest user (not logged in) + + # TODO Add admin. Commented out until admin column is added to users table + # if user.admin? + # can :manage, :all + # else + # can :read, :all + # end + + can :manage, Post do |post| + post.user == user + end + + can :manage, Comment do |comment| + comment.user == user || comment.post.user == user + end + + + # Define abilities for the passed in user here. For example: + # + # user ||= User.new # guest user (not logged in) + # if user.admin? + # can :manage, :all + # else + # can :read, :all + # end + # + # The first argument to `can` is the action you are giving the user + # permission to do. + # If you pass :manage it will apply to every action. Other common actions + # here are :read, :create, :update and :destroy. + # + # The second argument is the resource the user can perform the action on. + # If you pass :all it will apply to every resource. Otherwise pass a Ruby + # class of the resource. + # + # The third argument is an optional hash of conditions to further filter the + # objects. + # For example, here the user can only update published articles. + # + # can :update, Article, :published => true + # + # See the wiki for details: + # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities + end +end diff --git a/app/models/category.rb b/app/models/category.rb new file mode 100644 index 0000000..9dc026c --- /dev/null +++ b/app/models/category.rb @@ -0,0 +1,5 @@ +class Category < ActiveRecord::Base + has_many :posts, dependent: :nullify + + validates :title, presence: true, uniqueness: true +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..c5ab164 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,7 @@ +class Comment < ActiveRecord::Base + belongs_to :customer + belongs_to :post + belongs_to :user + + validates :body, presence: :true +end diff --git a/app/models/contact.rb b/app/models/contact.rb new file mode 100644 index 0000000..26a68e5 --- /dev/null +++ b/app/models/contact.rb @@ -0,0 +1,3 @@ +class Contact < ActiveRecord::Base + +end diff --git a/app/models/favourite.rb b/app/models/favourite.rb new file mode 100644 index 0000000..40ae00c --- /dev/null +++ b/app/models/favourite.rb @@ -0,0 +1,6 @@ +class Favourite < ActiveRecord::Base + belongs_to :user + belongs_to :post + + validates :post_id, uniqueness: {scope: :user_id} +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 0000000..8cf2578 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,29 @@ +class Post < ActiveRecord::Base + # title should be required & unique + validates :title, presence: true, uniqueness: true, length: {minimum: 7} + validates :body, presence: true + + has_many :comments, dependent: :nullify + belongs_to :category + belongs_to :user + + has_many :favourites, dependent: :nullify + has_many :favouriting_users, through: :favourites, source: :users + + def body_snippet + body.length > 99 ? "#{self.body[0...99]}..." : body + end + + def category_or_unknown + category ? category.title : "Unknown Category" + end + + def favourite_for(user) + favourites.find_by_user_id user if user + end + + def favourited_by?(user) + favourites.find_by_user_id(user.id).present? + end + +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..56bc805 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,17 @@ +class User < ActiveRecord::Base + has_many :posts + has_many :comments + + has_secure_password + + validates :first_name, presence: true + validates :last_name, presence: true + + VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i + validates :email, presence: true, uniqueness: true, format: VALID_EMAIL_REGEX + + validates :password, presence: true + + has_many :favourites, dependent: :nullify + has_many :favourite_posts, through: :favourites, source: :post +end diff --git a/app/views/comments/_comment.html.slim b/app/views/comments/_comment.html.slim new file mode 100644 index 0000000..3006469 --- /dev/null +++ b/app/views/comments/_comment.html.slim @@ -0,0 +1,14 @@ +div + span + = comment.body + | by + = comment.user.email + | + span + - if can? :delete, comment + = link_to "delete", post_comment_path(@post, comment), + data: {confirm: "Are you sure?"}, + method: :delete, + remote: true, + class: "glyphicon glyphicon-remove" + diff --git a/app/views/comments/_comment_form.html.slim b/app/views/comments/_comment_form.html.slim new file mode 100644 index 0000000..d0ca6d4 --- /dev/null +++ b/app/views/comments/_comment_form.html.slim @@ -0,0 +1,3 @@ += simple_form_for @comment do |f| + = f.input :body, + = f.submit diff --git a/app/views/comments/_form.html.slim b/app/views/comments/_form.html.slim new file mode 100644 index 0000000..971c33a --- /dev/null +++ b/app/views/comments/_form.html.slim @@ -0,0 +1,5 @@ += comment.errors.full_messages.join(", ") += form_for comment, url: post_comments_path(@post) do |f| + = form_for [@post, comment], remote: true do |form_helper| + = form_helper.text_area :body, class: "form-control" + = form_helper.submit diff --git a/app/views/comments/edit.html.slim b/app/views/comments/edit.html.slim new file mode 100644 index 0000000..79977c3 --- /dev/null +++ b/app/views/comments/edit.html.slim @@ -0,0 +1,3 @@ +h2 Edit Comment + += render "comment_form" diff --git a/app/views/comments/index.html.slim b/app/views/comments/index.html.slim new file mode 100644 index 0000000..af48bec --- /dev/null +++ b/app/views/comments/index.html.slim @@ -0,0 +1,5 @@ +h2 All Comments List + +- @comments.each do |c| + div + = c.body diff --git a/app/views/comments/new.html.slim b/app/views/comments/new.html.slim new file mode 100644 index 0000000..9557503 --- /dev/null +++ b/app/views/comments/new.html.slim @@ -0,0 +1,3 @@ +h2 New Comment + += render "comments/comment_form" diff --git a/app/views/comments/reload.js.erb b/app/views/comments/reload.js.erb new file mode 100644 index 0000000..5347512 --- /dev/null +++ b/app/views/comments/reload.js.erb @@ -0,0 +1 @@ +$("#comments").html("<%= j render 'comment' %>"); diff --git a/app/views/comments/show.html.slim b/app/views/comments/show.html.slim new file mode 100644 index 0000000..268dd5e --- /dev/null +++ b/app/views/comments/show.html.slim @@ -0,0 +1 @@ += @comment.body diff --git a/app/views/favourites/_favourite.html.erb b/app/views/favourites/_favourite.html.erb new file mode 100644 index 0000000..45707b0 --- /dev/null +++ b/app/views/favourites/_favourite.html.erb @@ -0,0 +1,5 @@ +<% if user_favourite.present? %> + <%= link_to "Unfavourite", post_favourite_path(@post, user_favourite), method: :delete, remote: true, class: "glyphicon glyphicon-star" %> +<% else %> + <%= link_to "Favourite", post_favourites_path(@post), method: :post, remote: true, class: "glyphicon glyphicon-star-empty" %> +<% end %> diff --git a/app/views/favourites/index.html.slim b/app/views/favourites/index.html.slim new file mode 100644 index 0000000..5fa22d7 --- /dev/null +++ b/app/views/favourites/index.html.slim @@ -0,0 +1,5 @@ +h1 Your Favourite Products + +- @favourites.each do |favourite| + div + = link_to favourite.post.title, post_path(favourite.post) diff --git a/app/views/favourites/modify_favourite.js.erb b/app/views/favourites/modify_favourite.js.erb new file mode 100644 index 0000000..7bd22e5 --- /dev/null +++ b/app/views/favourites/modify_favourite.js.erb @@ -0,0 +1 @@ +$("#favourite").html("<%= j render 'favourite' %>"); diff --git a/app/views/home/about.html.erb b/app/views/home/about.html.erb new file mode 100644 index 0000000..e685f80 --- /dev/null +++ b/app/views/home/about.html.erb @@ -0,0 +1,4 @@ +

About Page

+ + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi at purus ut velit placerat egestas ut a nibh. Pellentesque pulvinar, justo quis consequat vestibulum, sapien nisi bibendum est, et molestie leo tellus vitae erat. Maecenas sed bibendum enim. Cras in libero quis eros sollicitudin commodo ac et leo. Suspendisse venenatis metus a vestibulum gravida. Pellentesque fringilla augue vel eros fringilla, eu porta nulla lobortis. Sed porta ligula ut lacus accumsan, nec facilisis neque egestas.

diff --git a/app/views/home/home.html.erb b/app/views/home/home.html.erb new file mode 100644 index 0000000..fe1d011 --- /dev/null +++ b/app/views/home/home.html.erb @@ -0,0 +1 @@ +

Home Page

diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index d0ba841..c45cb01 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -7,8 +7,35 @@ <%= csrf_meta_tags %> +
+ +
+ <% if notice %> +
<%= notice %>
+ <% elsif alert %> +
<%= alert %>
+ <% end %> +
-<%= yield %> - + <%= yield %> +
diff --git a/app/views/posts/_post_form.html.slim b/app/views/posts/_post_form.html.slim new file mode 100644 index 0000000..3b00133 --- /dev/null +++ b/app/views/posts/_post_form.html.slim @@ -0,0 +1,12 @@ += form_for @post do |f| + div + = f.label :title + = f.text_field :title, class: "form-control" + div + = f.label :body + = f.text_area :body, class: "form-control" + div + = f.label :category + = f.collection_select :category_id, Category.order(:title), :id, :title + div + = f.submit "Submit", class: "btn btn-primary" diff --git a/app/views/posts/edit.html.slim b/app/views/posts/edit.html.slim new file mode 100644 index 0000000..960edf8 --- /dev/null +++ b/app/views/posts/edit.html.slim @@ -0,0 +1,3 @@ +h2 Editing Blog Post + += render "posts/post_form" diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb new file mode 100644 index 0000000..7477a92 --- /dev/null +++ b/app/views/posts/index.html.erb @@ -0,0 +1,12 @@ +

Blog Posts

+<% @posts.each do |p| %> +
+ <%= link_to p.title, post_path(p) %> +
+
+ Category: <%= p.category_or_unknown %> +
+
+ <%= p.body %> +
+<% end %> diff --git a/app/views/posts/new.html.slim b/app/views/posts/new.html.slim new file mode 100644 index 0000000..1d07049 --- /dev/null +++ b/app/views/posts/new.html.slim @@ -0,0 +1,3 @@ +h2 New Blog Post + += render "posts/post_form" diff --git a/app/views/posts/show.html.slim b/app/views/posts/show.html.slim new file mode 100644 index 0000000..0dc34da --- /dev/null +++ b/app/views/posts/show.html.slim @@ -0,0 +1,25 @@ +div + h2 + = @post.title +div + | Category: + = @post.category_or_unknown +div + = @post.body +- if can? :edit, @post + div + = link_to "Edit", edit_post_path(@post), class: "glyphicon glyphicon-edit" +- if can? :destroy, @post + div + = link_to "Delete", post_path(@post), method: :delete, data: {confirm: "Are you sure you want to delete this post?"}, class: "glyphicon glyphicon-trash" + +div id="favourite" + = render 'favourites/favourite' + +h3 Comments +div id="comment_form" + = render "/comments/form", comment: @comment + +div id="comments" + - @post.comments.each do |comment| + = render "/comments/comment", comment: comment diff --git a/app/views/sessions/new.html.slim b/app/views/sessions/new.html.slim new file mode 100644 index 0000000..2a0ed2a --- /dev/null +++ b/app/views/sessions/new.html.slim @@ -0,0 +1,11 @@ +h1 Sign In + += form_tag sessions_path + div + = label_tag :email + = email_field_tag :email + div + = label_tag :password + = password_field_tag :password + = submit_tag "Sign In" + diff --git a/app/views/users/_users_form.html.erb b/app/views/users/_users_form.html.erb new file mode 100644 index 0000000..ee1d063 --- /dev/null +++ b/app/views/users/_users_form.html.erb @@ -0,0 +1,33 @@ +<% if @user.errors.any? %> + +<% end %> + +<%= form_for @user do |f| %> +
+ <%= f.label :first_name %> + <%= f.text_field :first_name %> +
+
+ <%= f.label :last_name %> + <%= f.text_field :last_name %> +
+
+ <%= f.label :email %> + <%= f.email_field :email %> +
+
+ <%= f.label :password %> + <%= f.password_field :password %> +
+
+ <%= f.label :password_confirmation %> + <%= f.password_field :password_confirmation %> +
+
+ <%= f.submit "Create or modify an Account" %> +
+<% end %> diff --git a/app/views/users/edit.html.slim b/app/views/users/edit.html.slim new file mode 100644 index 0000000..4362912 --- /dev/null +++ b/app/views/users/edit.html.slim @@ -0,0 +1,3 @@ +h1 Edit User + += render "users/users_form" diff --git a/app/views/users/new.html.slim b/app/views/users/new.html.slim new file mode 100644 index 0000000..3d84bc0 --- /dev/null +++ b/app/views/users/new.html.slim @@ -0,0 +1,3 @@ +h1 Create User + += render "users/users_form" diff --git a/config/routes.rb b/config/routes.rb index 3f66539..237bf73 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,23 @@ Rails.application.routes.draw do + + get "/home" => "home#home" + + get "/about" => "home#about" + + resources :posts do + resources :comments + resources :favourites, only: [:create, :destroy] + end + resources :favourites, only: [:index] + + resources :users + + resources :sessions, only: [:new, :create] do + delete :destroy, on: :collection + end + + root 'home#home' + # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". diff --git a/db/migrate/20160411050204_create_posts.rb b/db/migrate/20160411050204_create_posts.rb new file mode 100644 index 0000000..691c505 --- /dev/null +++ b/db/migrate/20160411050204_create_posts.rb @@ -0,0 +1,10 @@ +class CreatePosts < ActiveRecord::Migration + def change + create_table :posts do |t| + t.string :title + t.text :body + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160411050207_create_comments.rb b/db/migrate/20160411050207_create_comments.rb new file mode 100644 index 0000000..bb50905 --- /dev/null +++ b/db/migrate/20160411050207_create_comments.rb @@ -0,0 +1,9 @@ +class CreateComments < ActiveRecord::Migration + def change + create_table :comments do |t| + t.text :body + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160411050217_create_categories.rb b/db/migrate/20160411050217_create_categories.rb new file mode 100644 index 0000000..38b9e13 --- /dev/null +++ b/db/migrate/20160411050217_create_categories.rb @@ -0,0 +1,9 @@ +class CreateCategories < ActiveRecord::Migration + def change + create_table :categories do |t| + t.string :title + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160411050227_create_contacts.rb b/db/migrate/20160411050227_create_contacts.rb new file mode 100644 index 0000000..5298b22 --- /dev/null +++ b/db/migrate/20160411050227_create_contacts.rb @@ -0,0 +1,12 @@ +class CreateContacts < ActiveRecord::Migration + def change + create_table :contacts do |t| + t.string :email + t.string :name + t.string :subject + t.text :message + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160411055429_attach_comments_to_posts.rb b/db/migrate/20160411055429_attach_comments_to_posts.rb new file mode 100644 index 0000000..8651677 --- /dev/null +++ b/db/migrate/20160411055429_attach_comments_to_posts.rb @@ -0,0 +1,5 @@ +class AttachCommentsToPosts < ActiveRecord::Migration + def change + add_reference :comments, :post + end +end diff --git a/db/migrate/20160417212436_add_category_references_to_posts.rb b/db/migrate/20160417212436_add_category_references_to_posts.rb new file mode 100644 index 0000000..e3e34bf --- /dev/null +++ b/db/migrate/20160417212436_add_category_references_to_posts.rb @@ -0,0 +1,5 @@ +class AddCategoryReferencesToPosts < ActiveRecord::Migration + def change + add_reference :posts, :category, index: true, foreign_key: true + end +end diff --git a/db/migrate/20160418235427_create_users.rb b/db/migrate/20160418235427_create_users.rb new file mode 100644 index 0000000..e22193e --- /dev/null +++ b/db/migrate/20160418235427_create_users.rb @@ -0,0 +1,12 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :first_name + t.string :last_name + t.string :email + t.string :password_digest + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160419000523_add_user_references_to_posts.rb b/db/migrate/20160419000523_add_user_references_to_posts.rb new file mode 100644 index 0000000..a74b33b --- /dev/null +++ b/db/migrate/20160419000523_add_user_references_to_posts.rb @@ -0,0 +1,5 @@ +class AddUserReferencesToPosts < ActiveRecord::Migration + def change + add_reference :posts, :user, index: true, foreign_key: true + end +end diff --git a/db/migrate/20160419000537_add_user_references_to_comments.rb b/db/migrate/20160419000537_add_user_references_to_comments.rb new file mode 100644 index 0000000..9d6a655 --- /dev/null +++ b/db/migrate/20160419000537_add_user_references_to_comments.rb @@ -0,0 +1,5 @@ +class AddUserReferencesToComments < ActiveRecord::Migration + def change + add_reference :comments, :user, index: true, foreign_key: true + end +end diff --git a/db/migrate/20160515170755_create_favourites.rb b/db/migrate/20160515170755_create_favourites.rb new file mode 100644 index 0000000..bf98360 --- /dev/null +++ b/db/migrate/20160515170755_create_favourites.rb @@ -0,0 +1,10 @@ +class CreateFavourites < ActiveRecord::Migration + def change + create_table :favourites do |t| + t.references :user, index: true, foreign_key: true + t.references :post, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..840f3ab --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,80 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20160515170755) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "categories", force: :cascade do |t| + t.string "title" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "comments", force: :cascade do |t| + t.text "body" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "post_id" + t.integer "user_id" + end + + add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree + + create_table "contacts", force: :cascade do |t| + t.string "email" + t.string "name" + t.string "subject" + t.text "message" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "favourites", force: :cascade do |t| + t.integer "user_id" + t.integer "post_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "favourites", ["post_id"], name: "index_favourites_on_post_id", using: :btree + add_index "favourites", ["user_id"], name: "index_favourites_on_user_id", using: :btree + + create_table "posts", force: :cascade do |t| + t.string "title" + t.text "body" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "category_id" + t.integer "user_id" + end + + add_index "posts", ["category_id"], name: "index_posts_on_category_id", using: :btree + add_index "posts", ["user_id"], name: "index_posts_on_user_id", using: :btree + + create_table "users", force: :cascade do |t| + t.string "first_name" + t.string "last_name" + t.string "email" + t.string "password_digest" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_foreign_key "comments", "users" + add_foreign_key "favourites", "posts" + add_foreign_key "favourites", "users" + add_foreign_key "posts", "categories" + add_foreign_key "posts", "users" +end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..b3666e5 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,12 @@ # # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) # Mayor.create(name: 'Emanuel', city: cities.first) + +# B log post seed +100.times do + Post.create title: Faker::Company.bs, + body: Faker::Lorem.paragraph +end + +# category seed. shouldn't run too often +# 10.times { FactoryGirl.create(:category) } diff --git a/spec/controllers/favourites_controller_spec.rb b/spec/controllers/favourites_controller_spec.rb new file mode 100644 index 0000000..b163a08 --- /dev/null +++ b/spec/controllers/favourites_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe FavouritesController, type: :controller do + +end diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb new file mode 100644 index 0000000..8bcdee7 --- /dev/null +++ b/spec/controllers/posts_controller_spec.rb @@ -0,0 +1,66 @@ +require 'rails_helper' + +RSpec.describe PostsController, type: :controller do + + before {get :new} + + describe "#new" do + it "renders the template" do + expect(response).to render_template(:new) + end + it "assigns a post object" do + expect(assigns(:post)).to be_a_new(Post) + end + end + + describe "#create" do + describe "with valid attributes" do + def valid_request + post :create, post: FactoryGirl.attributes_for(:post) + end + + it "saves a record to the database" do + count_before = Post.count + valid_request + count_after = Post.count + expect(count_after).to eq(count_before + 1) + end + + it "redirects_to the posts show page" do + valid_request + expect(response).to redirect_to post_path(Post.last) + end + + it "sets a flash message" do + valid_request + expect(flash[:notice]).to be + end + end + + describe "with invalid attributes" do + def invalid_request + post :create, post: {hello: "world"} + end + + it "renders the new template" do + invalid_request + expect(response).to render_template(:new) + end + + it "sets an alert message" do + invalid_request + expect(flash[:alert]).to be + end + + it "doesn't save a record to the database" do + count_before = Post.count + invalid_request + count_after = Post.count + expect(count_after).to eq(count_before) + end + + end + + end + +end diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb new file mode 100644 index 0000000..003bede --- /dev/null +++ b/spec/controllers/sessions_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe SessionsController, type: :controller do + +end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..d6ac064 --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,65 @@ +require 'rails_helper' + +RSpec.describe UsersController, type: :controller do + + describe "#new" do + it "renders the new template" do + get :new + expect(response).to render_template(:new) + end + + it "assigns a new user variable" do + get :new + expect(assigns(:user)).to be_a_new(User) + end + end + + describe "#create" do + def valid_request + post :create, user: FactoryGirl.attributes_for(:user) + end + + describe "with valid attributes" do + + it "redirects to the home page" do + valid_request + expect(response).to redirect_to(root_path) + end + + it "adds the new user to session" do + valid_request + expect(session[:user_id]).to eq(User.last.id) + end + + it "adds a record to the database" do + expect { valid_request }.to change { User.count }.by(1) + end + + it "redirects_to the home page" do + valid_request + expect(response).to redirect_to root_path + end + end + + describe "with invalid attributes" do + def invalid_request + post :create, user: FactoryGirl.attributes_for(:user).merge(email: nil) + end + + it "doesn't add a user to session" do + invalid_request + expect(session[:user_id]).to eq(nil) + end + + it "doesn't add a record to the database" do + expect { invalid_request }.to change { User.count }.by(0) + end + + it "renders the new template" do + invalid_request + expect(response).to render_template(:new) + end + + end + end +end diff --git a/spec/factories/categories.rb b/spec/factories/categories.rb new file mode 100644 index 0000000..938cb1d --- /dev/null +++ b/spec/factories/categories.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :category do + sequence(:title) {|n| "#{n}-#{Faker::Hacker.adjective}" } + end +end diff --git a/spec/factories/favourites.rb b/spec/factories/favourites.rb new file mode 100644 index 0000000..aa92c59 --- /dev/null +++ b/spec/factories/favourites.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :favourite do + user nil + post nil + end +end diff --git a/spec/factories/posts.rb b/spec/factories/posts.rb new file mode 100644 index 0000000..47ed04d --- /dev/null +++ b/spec/factories/posts.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :post do + sequence(:title) { |n| "#{Faker::Company.bs}-#{n}"} + body { Faker::Hipster.paragraph } + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..d6cf6ee --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :user do + first_name { Faker::Name.first_name } + last_name { Faker::Name.last_name } + sequence(:email) { |n| Faker::Internet.email.gsub("@", "-#{n}@") } + password { Faker::Internet.password } + end +end diff --git a/spec/helpers/favourites_helper_spec.rb b/spec/helpers/favourites_helper_spec.rb new file mode 100644 index 0000000..8c67e2c --- /dev/null +++ b/spec/helpers/favourites_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the FavouritesHelper. For example: +# +# describe FavouritesHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe FavouritesHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb new file mode 100644 index 0000000..9484198 --- /dev/null +++ b/spec/helpers/sessions_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the SessionsHelper. For example: +# +# describe SessionsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe SessionsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb new file mode 100644 index 0000000..b2e3444 --- /dev/null +++ b/spec/helpers/users_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the UsersHelper. For example: +# +# describe UsersHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe UsersHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb new file mode 100644 index 0000000..3d4c264 --- /dev/null +++ b/spec/models/category_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe Category, type: :model do + describe "validations" do + it "requires a title" do + c = Category.new(title: nil) + c.valid? + expect(c.errors).to have_key :title + end + it "must be a unique category" do + Category.create(title: "sample title") + c = Category.new(title: "sample title") + + c.valid? + + expect(c.errors).to have_key :title + + end + end +end diff --git a/spec/models/favourite_spec.rb b/spec/models/favourite_spec.rb new file mode 100644 index 0000000..271aef4 --- /dev/null +++ b/spec/models/favourite_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Favourite, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb new file mode 100644 index 0000000..09b24d7 --- /dev/null +++ b/spec/models/post_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +RSpec.describe Post, type: :model do + # pending "add some examples to (or delete) #{__FILE__}" + + describe "validations" do + def valid_attributes + {title: "Real Title", body: "A body goes here"} + end + + it "requires a title" do + p0 = Post.create(valid_attributes.merge title: nil) + p0.valid? + expect(p0.errors).to have_key(:title) + end + + it "must have a title longer than 7 characters" do + p0 = Post.new(valid_attributes.merge(title: "123")) + p0.valid? + expect(p0.errors).to have_key(:title) + + end + + it "requires a unique title" do + Post.create(valid_attributes) + p1 = Post.new(valid_attributes) + p1.valid? + + expect(p1.errors).to have_key :title + end + + it "requires a body" do + p0 = Post.new valid_attributes.merge body: nil + p0.valid? + expect(p0.errors).to have_key(:body) + end + + it "must have a method, body_snippet, that returns a shortened body" do + body = Faker::Lorem.characters(99) + # I appended "abcd" to push it over 100 characters and so I could experiment manualy + p0 = Post.new(title:"title", body: "#{body}abcd") + expect(p0.body_snippet).to eq("#{body}...") + end + + it "must have a method, body_snippet, that doesn't change small body strings " do + body = Faker::Lorem.characters(99) + p0 = Post.new(title:"title", body: body) + expect(p0.body_snippet).to eq(body) + end + + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..8fe9b7c --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,53 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + + describe "validations" do + def valid_attributes + {first_name: "Joe", last_name: "Blogs", email: "email@hotmail.com", password: "supersecret"} + end + + it "requires a first_name" do + u = User.new(valid_attributes.merge first_name: nil) + u.valid? + expect(u.errors).to have_key(:first_name) + end + + it "requires a last_name" do + u = User.new(valid_attributes.merge last_name: nil) + u.valid? + expect(u.errors).to have_key(:last_name) + end + + it "requires a email" do + u = User.new(valid_attributes.merge email: nil) + u.valid? + expect(u.errors).to have_key(:email) + end + it "requires a email with an @" do + u = User.new(valid_attributes.merge email: "not_a_real_email") + u.valid? + expect(u.errors).to have_key(:email) + end + it "requires a unique email" do + User.create(valid_attributes) + e = User.new(valid_attributes) + e.valid? + + expect(e.errors).to have_key :email + end + + it "requires a password" do + u = User.new(valid_attributes.merge password: nil) + u.valid? + expect(u.errors).to have_key(:password) + end + + it "hashes the password" do + pass = "supersecret" + u = User.new(valid_attributes.merge password: pass) + expect(u.password_digest).not_to eq(pass) + end + end + +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..6f1ab14 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,57 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'spec_helper' +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +# Checks for pending migration and applies them before tests are run. +# If you are not using ActiveRecord, you can remove this line. +ActiveRecord::Migration.maintain_test_schema! + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..61e2738 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,92 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# The `.rspec` file also contains a few flags that are not defaults but that +# users commonly want. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end