diff --git a/.byebug_history b/.byebug_history new file mode 100644 index 0000000..2da14fd --- /dev/null +++ b/.byebug_history @@ -0,0 +1,3 @@ +exit +Question.questions_sorted_by_most_votes + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ddd068 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/rspec +coverage/ +log/test.log \ No newline at end of file diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..43ae203 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--color +--require spec_helper +--format documentation diff --git a/Gemfile b/Gemfile index c5072c8..de4593a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' - +ruby '2.2.3' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '4.2.5' @@ -32,19 +32,21 @@ gem 'bcrypt', '~> 3.1.7' group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console - gem 'byebug' gem 'rails_12factor' gem 'rspec-rails' gem 'simplecov', :require => false, :group => :test gem 'shoulda-matchers', '~> 3.0' + gem 'factory_girl_rails' end group :development do # Access an IRB console on exception pages or by using <%= console %> in views gem 'web-console', '~> 2.0' gem 'faker' - gem 'factory_girl_rails' - + gem 'capybara' + gem 'database_cleaner' + gem 'launchy' + gem 'selenium-webdriver' # 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 a2c6bbe..a8ad52b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -36,13 +36,22 @@ GEM minitest (~> 5.1) thread_safe (~> 0.3, >= 0.3.4) tzinfo (~> 1.1) + addressable (2.4.0) arel (6.0.3) bcrypt (3.1.10) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) builder (3.2.2) - byebug (8.2.1) + capybara (2.5.0) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.8) + ffi (~> 1.0, >= 1.0.11) concurrent-ruby (1.0.0) + database_cleaner (1.5.1) debug_inspector (0.0.2) diff-lcs (1.2.5) docile (1.1.5) @@ -55,6 +64,7 @@ GEM railties (>= 3.0.0) faker (1.6.1) i18n (~> 0.5) + ffi (1.9.10) globalid (0.3.6) activesupport (>= 4.1.0) i18n (0.7.0) @@ -66,6 +76,8 @@ GEM railties (>= 4.2.0) thor (>= 0.14, < 2.0) json (1.8.3) + launchy (2.4.3) + addressable (~> 2.3) loofah (2.0.3) nokogiri (>= 1.5.9) mail (2.6.3) @@ -129,6 +141,7 @@ GEM rspec-mocks (~> 3.4.0) rspec-support (~> 3.4.0) rspec-support (3.4.1) + rubyzip (1.1.7) sass (3.4.20) sass-rails (5.0.4) railties (>= 4.0.0, < 5.0) @@ -139,6 +152,11 @@ GEM sdoc (0.4.1) json (~> 1.7, >= 1.7.7) rdoc (~> 4.0) + selenium-webdriver (2.48.1) + childprocess (~> 0.5) + multi_json (~> 1.0) + rubyzip (~> 1.0) + websocket (~> 1.0) shoulda-matchers (3.0.1) activesupport (>= 4.0.0) simplecov (0.11.1) @@ -167,23 +185,29 @@ GEM binding_of_caller (>= 0.7.2) railties (>= 4.0) sprockets-rails (>= 2.0, < 4.0) + websocket (1.2.2) + xpath (2.0.0) + nokogiri (~> 1.3) PLATFORMS ruby DEPENDENCIES bcrypt (~> 3.1.7) - byebug + capybara + database_cleaner factory_girl_rails faker jbuilder (~> 2.0) jquery-rails + launchy pg (~> 0.15) rails (= 4.2.5) rails_12factor rspec-rails sass-rails (~> 5.0) sdoc (~> 0.4.0) + selenium-webdriver shoulda-matchers (~> 3.0) simplecov spring diff --git a/IMG_2113.JPG b/IMG_2113.JPG new file mode 100644 index 0000000..21a776f Binary files /dev/null and b/IMG_2113.JPG differ diff --git a/IMG_2114.JPG b/IMG_2114.JPG new file mode 100644 index 0000000..5890574 Binary files /dev/null and b/IMG_2114.JPG differ diff --git a/README.rdoc b/README.rdoc index dd4e97e..ccdae82 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,5 +1,10 @@ == README + +Trello: + +https://trello.com/b/xtk06JMI/overflow + This README would normally document whatever steps are necessary to get the application up and running. diff --git a/app/assets/stylesheets/site_css.css b/app/assets/stylesheets/site_css.css new file mode 100644 index 0000000..4ccbafb --- /dev/null +++ b/app/assets/stylesheets/site_css.css @@ -0,0 +1,53 @@ + +body { + background-color: #eeeeee; + font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; + font-weight: 500; + font-size: 14px; + color: #1e1e1e; +} + +h1 { + font-family: Georgia, serif; + font-style: italic; + color: #BA55D3; +} + +h2 { + font-family: Georgia, serif; + font-style: italic; + color: #BA55D3; +} + +nav { + background-color: #004060; + color: #ffffff; + padding: 4px 8px; +} + +li { + color: #BA55D3 ; +} + +p3 { + font-style: italic; + color: #008000; +} + +p2 { + font-style: italic; + color: #DC143C; +} + +.question_header { + border: dotted black 2px ; +} + +.answer_header { + margin-left: 5%; + font-size: 1.5em; +} + +.comments { + margin-left: 5%; +} diff --git a/app/controllers/answers_controller.rb b/app/controllers/answers_controller.rb new file mode 100644 index 0000000..bdfc328 --- /dev/null +++ b/app/controllers/answers_controller.rb @@ -0,0 +1,20 @@ +class AnswersController < ApplicationController + before_action :ensure_logged_in, only:[:create] + def create + question = Question.find_by(id: params[:question_id]) + @answer = current_user.answers.build(answer_params) + @answer.question = question + if @answer.save + redirect_to question_path(question.id) + else + flash[:notice] = "Something went wrong." + redirect_to question_path(question.id) + end + end + + def answer_params + params.require(:answer).permit(:body) + end +end + + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index d83690e..98957b7 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,4 +2,17 @@ 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 + helper_method :current_user, :logged_in? + + def current_user + @current_user ||= User.find_by(id: session[:user_id]) + end + + def logged_in? + !!session[:user_id] + end + + def ensure_logged_in + redirect_to login_path unless current_user + end end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 0000000..1beed42 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,36 @@ +class CommentsController < ApplicationController + before_action :ensure_logged_in, only:[:create] + def new + @comment = Comment.new + end + + def create + if (params.has_key?(:answer_id)) + @answer = Answer.find_by(id: params[:answer_id]) + comment = current_user.comments.build(comment_params) + comment.commentable_id = params[:answer_id] + comment.commentable_type = "Answer" + params[:question_id] = @answer.question.id + else + comment = current_user.comments.build(comment_params) + comment.commentable_type = "Question" + comment.commentable_id = params[:question_id] + end + + if comment.save + redirect_to question_path(params[:question_id]) #have to pass in question.id as a local in the question show page when the comment is to an answer + else + render 'new' + end + end + +private + + def comment_params + params.require(:comment).permit(:body) + end + +end + + + diff --git a/app/controllers/questions_controller.rb b/app/controllers/questions_controller.rb new file mode 100644 index 0000000..b04b20a --- /dev/null +++ b/app/controllers/questions_controller.rb @@ -0,0 +1,52 @@ +class QuestionsController < ApplicationController + before_action :ensure_logged_in, only:[:new, :create, :update] + def index + @questions = Question.all + end + + def trending + @questions = Question.trending_questions + render 'index' + end + + def voted + @questions = Question.top_voted_questions + render 'index' + end + + def new + @question = Question.new + end + + def create + question = current_user.questions.build(question_params) + if question.save + redirect_to root_path + else + render 'new' + end + end + + def show + @question = Question.find(params[:id]) + @answers = Answer.top_voted_answer(@question) + @answer = Answer.new + @comment = Comment.new + @vote = Vote.new + end + + def update + question = Question.find(params[:id]) + question.update_attributes(accepted_answer_id: params[:chosen_answer_id]) + redirect_to question_path(question) + end + + + +private + + def question_params + params.require(:question).permit(:title, :body) + end + +end diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..4ba79fd --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,25 @@ +class SessionsController < ApplicationController + skip_before_action :ensure_logged_in + + def new + end + + def create + user = User.find_by(username: params[:username]) + if user && user.authenticate(params[:password]) + flash[:notice] = 'Welcome to our site!' + session[:user_id] = user.id + redirect_to root_path + else + flash.now[:warning] = 'Login failed. Please Try Again.' + render :new + end + end + + def destroy + session.clear + flash[:notice] = 'Thanks For Visiting The Site! Please Return Soon!' + redirect_to root_path + end + +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..41cd9d5 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,26 @@ +class UsersController < ApplicationController + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + session[:user_id] = @user.id + flash[:notice] = 'Congratulations! You Are Now Registered!' + redirect_to root_path + else + render :new + end + end + + def edit + end + + def destroy + end + + def user_params + params.require(:user).permit(:username, :password, :password_confirmation) + end +end diff --git a/app/controllers/votes_controller.rb b/app/controllers/votes_controller.rb new file mode 100644 index 0000000..9ddae8e --- /dev/null +++ b/app/controllers/votes_controller.rb @@ -0,0 +1,46 @@ +class VotesController < ApplicationController + before_action :ensure_logged_in, only:[:create] + + def new + @vote = Vote.new + end + def create + if (params.has_key?(:comment_id)) + vote = current_user.votes.build(vote_params) + vote.votable_type = "Comment" + vote.votable_id = params[:comment_id] + comment = Comment.find_by(id: params[:comment_id]) + vote.author_id = comment.user.id + if comment.commentable_type == "Question" + params[:question_id] = comment.commentable.id + + else + params[:question_id] = comment.commentable.question.id + end + elsif (params.has_key?(:question_id)) + vote = current_user.votes.build(vote_params) + vote.votable_type = "Question" + vote.votable_id = params[:question_id] + vote.author_id = Question.find_by(id: params[:question_id]).user.id + else + vote = current_user.votes.build(vote_params) + vote.votable_type = "Answer" + vote.votable_id = params[:answer_id] + params[:question_id] = Answer.find_by(id: params[:answer_id]).question.id + vote.author_id = Answer.find_by(id: params[:answer_id]).user.id + end + + if vote.save + redirect_to question_path(params[:question_id]) + else + flash.now[:warning] = 'vote failed' + end + + end + + private + + def vote_params + params.require(:vote).permit(:value) + end +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be79..2f54db2 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,3 @@ module ApplicationHelper end + diff --git a/app/models/answer.rb b/app/models/answer.rb new file mode 100644 index 0000000..013e7f5 --- /dev/null +++ b/app/models/answer.rb @@ -0,0 +1,30 @@ +class Answer < ActiveRecord::Base + + has_many :comments, :as => :commentable + has_many :votes, :as => :votable + belongs_to :user + belongs_to :question + + def self.newest_answers + + Answer.order(:updated_at).reverse.group_by{|answer| answer.question_id}.map{|group| group[1][0]} + + end + + def self.top_voted_answer(question) + top = question.answers.select{|answer| answer.count_votes.is_a?(Integer)}.sort{|answer| answer.count_votes}.reverse + question.answers.each do |answer| + if top.exclude?(answer) + top << answer + end + end + if top.delete_if{|answer| answer.id == question.accepted_answer_id} + top.unshift(question.answers.select{|answer| answer.id == question.accepted_answer_id}.first) + end + top.compact + end + + def count_votes + votes.map{|vote| vote.value}.reduce(:+) + end +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 0000000..bb0284f --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,11 @@ +class Comment < ActiveRecord::Base + + has_many :votes, :as => :votable + belongs_to :user + belongs_to :commentable, polymorphic: true + + + def count_votes + votes.map{|vote| vote.value}.reduce(:+) + end +end diff --git a/app/models/question.rb b/app/models/question.rb new file mode 100644 index 0000000..4dbbe59 --- /dev/null +++ b/app/models/question.rb @@ -0,0 +1,40 @@ +class Question < ActiveRecord::Base + belongs_to :user + has_many :answers + has_many :comments, :as => :commentable + has_many :votes, :as => :votable + + def find_accepted_answer + self.answers.find(accepted_answer_id) + end + + def self.most_recent_questions + self.order(:created_at).reverse + end + + def self.trending_questions + Answer.newest_answers.map{|answer| answer.question} + end + + def self.top_voted_questions + Question.questions_sorted_by_most_votes + Question.questions_with_no_votes + end + + def count_votes + votes.map{|vote| vote.value}.reduce(:+) + end + + private + + def self.questions_sorted_by_most_votes + all.select{|question| question.count_votes.is_a?(Integer)}.sort_by{|question| question.count_votes}.reverse + end + + def self.questions_with_no_votes + all.select{|question| question.count_votes == nil} + end + +end + + +# Users should be able to see questions sorted three ways: highest-voted, most recent, and "trending." diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..95a6434 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,24 @@ +class User < ActiveRecord::Base + has_many :questions + has_many :answers + has_many :comments + has_many :votes + has_many :votes_for, class_name: "Vote", foreign_key: "author_id" + validates :username, uniqueness: true, presence: true, length: {minimum: 3} + validates :password, presence: true, length: {minimum: 6} + validates_confirmation_of :password, :confirm => :password_confirmation + + def password_confirmation_matches + unless password_confirmation == self.password + errors.add(:password_confirmation, "must match password") + end + end + + def vote_count + votes_for.map{|vote| vote.value}.reduce(:+) + end + + + + has_secure_password +end diff --git a/app/models/vote.rb b/app/models/vote.rb new file mode 100644 index 0000000..73d6dc6 --- /dev/null +++ b/app/models/vote.rb @@ -0,0 +1,6 @@ +class Vote < ActiveRecord::Base + belongs_to :user + belongs_to :votable, polymorphic: true + belongs_to :author, class_name: 'User' + validates_uniqueness_of :user_id, scope: [:votable_id, :votable_type] +end diff --git a/app/views/comments/_new.html.erb b/app/views/comments/_new.html.erb new file mode 100644 index 0000000..10f6481 --- /dev/null +++ b/app/views/comments/_new.html.erb @@ -0,0 +1,8 @@ +<%= form_for [commentable, @comment] do |f| %> + + <%= f.text_area :body %> + + <%= f.submit %> +<% end %> + + diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index c4e6bce..81426b3 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,12 +1,32 @@ - UnicornVsDragon + Stack Overload <%= stylesheet_link_tag 'application', media: 'all' %> <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %> + + +
+ <% flash.each do |name, msg| %> + <% if msg.is_a?(String) %> + <%= content_tag :div, msg, :class => "flash_#{name}" %> + <% end %> + <% end %> +
<%= yield %> diff --git a/app/views/questions/index.html.erb b/app/views/questions/index.html.erb new file mode 100644 index 0000000..75d2715 --- /dev/null +++ b/app/views/questions/index.html.erb @@ -0,0 +1,13 @@ +

List of Questions

+ +
+ <%= link_to "Ask a question", new_question_path %> +
+ + + <%= link_to "Trending", trending_path%> <%= link_to "Highest Voted", voted_path %> <%= link_to "Most Recent", questions_path %> +
    + <% @questions.each do |question| %> +
  1. <%=link_to question.title, question_path(question.id) %>
  2. + <% end %> +
diff --git a/app/views/questions/new.html.erb b/app/views/questions/new.html.erb new file mode 100644 index 0000000..3c49ae7 --- /dev/null +++ b/app/views/questions/new.html.erb @@ -0,0 +1,11 @@ +

Ask a question

+ +<%= form_for @question do |f| %> + <%= f.label :title %> + <%= f.text_field :title %> + + <%= f.label :body %> + <%= f.text_area :body %> + + <%= f.submit %> +<% end %> diff --git a/app/views/questions/show.html.erb b/app/views/questions/show.html.erb new file mode 100644 index 0000000..ad802e8 --- /dev/null +++ b/app/views/questions/show.html.erb @@ -0,0 +1,64 @@ +
+ +

<%=@question.title%>

+

Asked by: <%=@question.user.username%>

+

<%=@question.body%>

+

Points:<%=@question.count_votes%>

+ <%= render :partial =>"votes/new", :controller => "votes", locals: {votable: @question} %> + <% @question.comments.each do |comment| %> +
+

<%=comment.body%>

+

Points:<%=comment.count_votes%>

+ <%= render :partial =>"votes/new", :controller => "votes", locals: {votable: comment} %> +
+ <% end %> + + <%= render :partial =>"comments/new", :controller => "comments", locals: {commentable: @question} %> +
+ +
+<%@answers.each do |a|%> + +
+

<%=a.body%>

+

Answered by: <%=a.user.username%>

+

Points:<%=a.count_votes%>

+ <%= render :partial =>"votes/new", :controller => "votes", locals: {votable: a} %> + <%= render :partial =>"comments/new", :controller => "comments", locals: {commentable: a} %> +
+
+ <% a.comments.each do |comment| %> +

<%=comment.body%>

+

Points:<%=comment.count_votes%>

+ <%= render :partial =>"votes/new", :controller => "votes", locals: {votable: comment} %> + <% end %> +
+ <%if current_user == @question.user %> + <%= form_tag question_path, method: "put" do %> + <%= hidden_field_tag "chosen_answer_id", a.id %> + <%= submit_tag 'Pick This Answer' %> + <% end %> + <%end%> + +<% end %> +
+ +
+ <%= form_for [@question, @answer] do |f|%> + <%= f.label "Type Your Answer Here:" %> + <%= f.text_area :body %> + <%= f.submit "Submit Answer" %> + <% end %> + +
+ <% flash.each do |name, msg| %> + <% if msg.is_a?(String) %> + <%= content_tag :div, msg, :class => "flash_#{name}" %> + <% end %> + <% end %> +
+
+ + + + diff --git a/app/views/sessions/new.html.erb b/app/views/sessions/new.html.erb new file mode 100644 index 0000000..6612fb2 --- /dev/null +++ b/app/views/sessions/new.html.erb @@ -0,0 +1,12 @@ +<%= form_tag sessions_path, class:'login_form' do %> +
+<%= label_tag :username %> +<%= text_field_tag :username %> +
+
+<%= label_tag :password %> +<%= password_field_tag :password %> +
+ +<%= submit_tag 'Login' %> +<% end %> diff --git a/app/views/users/create.html.erb b/app/views/users/create.html.erb new file mode 100644 index 0000000..48ea02e --- /dev/null +++ b/app/views/users/create.html.erb @@ -0,0 +1,2 @@ +

Users#create

+

Find me in app/views/users/create.html.erb

diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb new file mode 100644 index 0000000..1881fbd --- /dev/null +++ b/app/views/users/edit.html.erb @@ -0,0 +1,2 @@ +

Users#edit

+

Find me in app/views/users/edit.html.erb

diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 0000000..e45412f --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,16 @@ +<%= form_for @user do |f| %> + <%= f.label :username %> + <%= f.text_field :username %> +

+ <%= f.label :password %> + <%= f.password_field :password %> + <%= f.label :password_confirmation %> + <%= f.password_field :password_confirmation %> + <%= f.submit 'Join' %> + +<% end %> +<% if defined?(@user.errors) %> + <% @user.errors.full_messages.each do |msg| %> +
  • <%= msg %>
  • + <% end %> +<% end %> diff --git a/app/views/votes/_new.html.erb b/app/views/votes/_new.html.erb new file mode 100644 index 0000000..5398f3e --- /dev/null +++ b/app/views/votes/_new.html.erb @@ -0,0 +1,9 @@ +<%= form_for [votable, @vote] do |f| %> + <%= f.label "+1" %> + <%= f.radio_button(:value, 1) %> + <%= f.label "-1" %> + <%= f.radio_button(:value, -1) %> + <%= f.submit "Vote!"%> +<% end %> + + diff --git a/config/application.rb b/config/application.rb index 6b38561..3ee6e29 100644 --- a/config/application.rb +++ b/config/application.rb @@ -15,6 +15,7 @@ # you've limited to :test, :development, or :production. Bundler.require(*Rails.groups) +module StackOverflow class Application < Rails::Application # Settings in config/environments/* take precedence over those specified here. # Application configuration should go into files in config/initializers diff --git a/config/database.yml b/config/database.yml index ad36b76..c434ac6 100644 --- a/config/database.yml +++ b/config/database.yml @@ -23,13 +23,13 @@ default: &default development: <<: *default - database: unicorn_vs_dragon_development + database: stack_overflow_development # The specified database role being used to connect to postgres. # To create additional roles in postgres see `$ createuser --help`. # When left blank, postgres will use the default role. This is # the same name as the operating system user that initialized the database. - #username: unicorn_vs_dragon + #username: # The password associated with the postgres role (username). #password: @@ -57,7 +57,7 @@ development: # Do not set this db to the same as development or production. test: <<: *default - database: unicorn_vs_dragon_test + database: stack_test # As with config/secrets.yml, you never want to store sensitive information, # like your database password, in your source code. If your source code is @@ -80,6 +80,6 @@ test: # production: <<: *default - database: unicorn_vs_dragon_production - username: unicorn_vs_dragon + database: stack_overflow_production + username: overflow password: <%= ENV['UNICORN_VS_DRAGON_DATABASE_PASSWORD'] %> diff --git a/config/routes.rb b/config/routes.rb index 3f66539..d6e7e73 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,6 +2,10 @@ # The priority is based upon order of creation: first created -> highest priority. # See how all your routes lay out with "rake routes". + root 'questions#index' + + get 'questions/trending' => 'questions#trending', as: :trending + get 'questions/voted' => 'questions#voted', as: :voted # You can have the root of your site routed with "root" # root 'welcome#index' @@ -53,4 +57,26 @@ # # (app/controllers/admin/products_controller.rb) # resources :products # end + + resources :users, only: [:new, :create] + + resources :sessions, only:[:create, :destroy] + + get 'login' => 'sessions#new' + get 'logout'=> 'sessions#destroy' + + + resources :questions do + resources :answers, only: [:create, :destroy] + resources :comments, only: [:new, :create] + resources :votes, only: [:create, :update] + end + resources :answers do + resources :comments, only:[:new, :create] + resources :votes, only: [:create, :update] + end + + resources :comments, only: [:new, :create] do + resources :votes, only: [:create, :update] + end end diff --git a/db/migrate/20151231194341_create_users.rb b/db/migrate/20151231194341_create_users.rb new file mode 100644 index 0000000..72a9f38 --- /dev/null +++ b/db/migrate/20151231194341_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :username + t.string :password_digest + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151231194536_create_questions.rb b/db/migrate/20151231194536_create_questions.rb new file mode 100644 index 0000000..d77e184 --- /dev/null +++ b/db/migrate/20151231194536_create_questions.rb @@ -0,0 +1,11 @@ +class CreateQuestions < ActiveRecord::Migration + def change + create_table :questions do |t| + t.string :title + t.string :body + t.references :user, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151231194616_create_answers.rb b/db/migrate/20151231194616_create_answers.rb new file mode 100644 index 0000000..7b10fa5 --- /dev/null +++ b/db/migrate/20151231194616_create_answers.rb @@ -0,0 +1,11 @@ +class CreateAnswers < ActiveRecord::Migration + def change + create_table :answers do |t| + t.string :body + t.references :user, index: true, foreign_key: true + t.references :question, index: true, foreign_key: true + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151231194834_create_comments.rb b/db/migrate/20151231194834_create_comments.rb new file mode 100644 index 0000000..7314606 --- /dev/null +++ b/db/migrate/20151231194834_create_comments.rb @@ -0,0 +1,11 @@ +class CreateComments < ActiveRecord::Migration + def change + create_table :comments do |t| + t.references :user, index: true, foreign_key: true + t.references :commentable, polymorphic: true, index: true + t.string :body + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20151231195101_create_votes.rb b/db/migrate/20151231195101_create_votes.rb new file mode 100644 index 0000000..561acdb --- /dev/null +++ b/db/migrate/20151231195101_create_votes.rb @@ -0,0 +1,11 @@ +class CreateVotes < ActiveRecord::Migration + def change + create_table :votes do |t| + t.references :user, index: true, foreign_key: true + t.references :votable, polymorphic: true, index: true + t.integer :value + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160103005011_add_accepted_answer_idto_questions.rb b/db/migrate/20160103005011_add_accepted_answer_idto_questions.rb new file mode 100644 index 0000000..7551655 --- /dev/null +++ b/db/migrate/20160103005011_add_accepted_answer_idto_questions.rb @@ -0,0 +1,5 @@ +class AddAcceptedAnswerIdtoQuestions < ActiveRecord::Migration + def change + add_column :questions, :accepted_answer_id, :integer + end +end diff --git a/db/migrate/20160103022855_add_column_to_user.rb b/db/migrate/20160103022855_add_column_to_user.rb new file mode 100644 index 0000000..401cd62 --- /dev/null +++ b/db/migrate/20160103022855_add_column_to_user.rb @@ -0,0 +1,5 @@ +class AddColumnToUser < ActiveRecord::Migration + def change + add_column :users, :author_id, :integer + end +end diff --git a/db/migrate/20160103184543_remove_author_id_from_users.rb b/db/migrate/20160103184543_remove_author_id_from_users.rb new file mode 100644 index 0000000..9e5f290 --- /dev/null +++ b/db/migrate/20160103184543_remove_author_id_from_users.rb @@ -0,0 +1,6 @@ +class RemoveAuthorIdFromUsers < ActiveRecord::Migration + def change + remove_column :users, :author_id, :integer + add_column :votes, :author_id, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 0000000..3bece20 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,78 @@ +# 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: 20160103210535) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "answers", force: :cascade do |t| + t.string "body" + t.integer "user_id" + t.integer "question_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "answers", ["question_id"], name: "index_answers_on_question_id", using: :btree + add_index "answers", ["user_id"], name: "index_answers_on_user_id", using: :btree + + create_table "comments", force: :cascade do |t| + t.integer "user_id" + t.integer "commentable_id" + t.string "commentable_type" + t.string "body" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "comments", ["commentable_type", "commentable_id"], name: "index_comments_on_commentable_type_and_commentable_id", using: :btree + add_index "comments", ["user_id"], name: "index_comments_on_user_id", using: :btree + + create_table "questions", force: :cascade do |t| + t.string "title" + t.string "body" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "accepted_answer_id" + end + + add_index "questions", ["user_id"], name: "index_questions_on_user_id", using: :btree + + create_table "users", force: :cascade do |t| + t.string "username" + t.string "password_digest" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + create_table "votes", force: :cascade do |t| + t.integer "user_id" + t.integer "votable_id" + t.string "votable_type" + t.integer "value", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "author_id" + end + + add_index "votes", ["user_id"], name: "index_votes_on_user_id", using: :btree + add_index "votes", ["votable_type", "votable_id"], name: "index_votes_on_votable_type_and_votable_id", using: :btree + + add_foreign_key "answers", "questions" + add_foreign_key "answers", "users" + add_foreign_key "comments", "users" + add_foreign_key "questions", "users" + add_foreign_key "votes", "users" +end diff --git a/db/seeds.rb b/db/seeds.rb index 4edb1e8..b6fb4d9 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,7 +1,14 @@ -# This file should contain all the record creation needed to seed the database with its default values. -# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). -# -# Examples: -# -# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) -# Mayor.create(name: 'Emanuel', city: cities.first) +u = User.create!(username: "christopher", password: "123456") +10.times do + q = Question.create!(title: Faker::Hipster.sentence(8), body: Faker::Hipster.sentence(10, true, 5), user_id: u.id) + 3.times do + c = Comment.create!(body: Faker::Hipster.sentence(10, true, 5), user_id: u.id, commentable_id: q.id, commentable_type: "Question") + end + 6.times do + a = Answer.create!(body: Faker::Hipster.sentence(10, true, 5), user_id: u.id, question_id: q.id) + 3.times do + c = Comment.create!(body: Faker::Hipster.sentence(10, true, 5), user_id: u.id, commentable_id: a.id, commentable_type: "Answer") + end + end +end + diff --git a/log/.keep b/log/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000..2e470db --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,33 @@ +# require 'rails_helper' + +# RSpec.describe UsersController, type: :controller do + +# describe "GET #new" do +# it "returns http success" do +# get :new +# expect(response).to have_http_status(:success) +# end +# end + +# describe "GET #create" do +# it "returns http success" do +# get :create +# expect(response).to have_http_status(:success) +# end +# end + +# describe "GET #edit" do +# it "returns http success" do +# get :edit +# expect(response).to have_http_status(:success) +# end +# end + +# describe "GET #destroy" do +# it "returns http success" do +# get :destroy +# expect(response).to have_http_status(:success) +# end +# end + +# end diff --git a/spec/factories.rb b/spec/factories.rb new file mode 100644 index 0000000..d045bcf --- /dev/null +++ b/spec/factories.rb @@ -0,0 +1,15 @@ + + + +# FactoryGirl.define do +# factory :user, aliases: [:author] do +# username "fakeaccount" +# password "password" +# end + +# factory :question do +# title Faker::Hipster.sentence(6) +# body Faker::Hipster.sentence(10, true, 5) +# user +# end +# end diff --git a/spec/factories/answers.rb b/spec/factories/answers.rb new file mode 100644 index 0000000..86cab62 --- /dev/null +++ b/spec/factories/answers.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :answer do + body "MyString" +user_id nil +question_id nil + end + +end diff --git a/spec/factories/comments.rb b/spec/factories/comments.rb new file mode 100644 index 0000000..3a6e3e5 --- /dev/null +++ b/spec/factories/comments.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :comment do + user_id nil +commentable nil +body "MyString" + end + +end diff --git a/spec/factories/questions.rb b/spec/factories/questions.rb new file mode 100644 index 0000000..07539bd --- /dev/null +++ b/spec/factories/questions.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :question do + title "MyString" +body "MyString" +user_id nil + end + +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb new file mode 100644 index 0000000..6dfba06 --- /dev/null +++ b/spec/factories/users.rb @@ -0,0 +1,7 @@ +FactoryGirl.define do + factory :user do + username "MyString" +password_digest "MyString" + end + +end diff --git a/spec/factories/votes.rb b/spec/factories/votes.rb new file mode 100644 index 0000000..511ff81 --- /dev/null +++ b/spec/factories/votes.rb @@ -0,0 +1,8 @@ +FactoryGirl.define do + factory :vote do + user nil +votable nil +value 1 + end + +end diff --git a/spec/models/answer_spec.rb b/spec/models/answer_spec.rb new file mode 100644 index 0000000..e594ea3 --- /dev/null +++ b/spec/models/answer_spec.rb @@ -0,0 +1,9 @@ +require 'rails_helper' + +RSpec.describe Answer, type: :model do + it {should have_many(:comments)} + it {should have_many(:votes)} + it {should belong_to(:user)} + it {should belong_to(:question)} + +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb new file mode 100644 index 0000000..c10688d --- /dev/null +++ b/spec/models/comment_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Comment, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/question_spec.rb b/spec/models/question_spec.rb new file mode 100644 index 0000000..fbe31c3 --- /dev/null +++ b/spec/models/question_spec.rb @@ -0,0 +1,7 @@ +# require 'rails_helper' + +# RSpec.describe Question, type: :model do +# it {should validate_presence_of (:title) } +# it {should validate_presence_of (:body) } +# it {should validate_presence_of (:user_id)} +# end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 0000000..d199407 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe User, type: :model do + it { should validate_presence_of(:username) } + it { should validate_uniqueness_of(:username) } + it { should validate_length_of(:username).is_at_least(3) } + it { should validate_presence_of(:password) } + it { should validate_length_of(:password).is_at_least(6) } + it { should have_many(:questions) } + it { should have_many(:answers) } + it { should have_many(:comments) } + it { should have_many(:votes) } + + it "is invalid without a username" do + user = User.new(username: nil, password_digest: "password") + expect(user).not_to be_valid + end + + it "is invalid without a password" do + user = User.new(username: "username", password_digest: nil) + expect(user).not_to be_valid + end + + # before(:each) do + # user = User.new @attr + # user.password = 'password' + # user.password_confirmation = 'password' + # user.save! + # end + + + + +end diff --git a/spec/models/vote_spec.rb b/spec/models/vote_spec.rb new file mode 100644 index 0000000..7f550be --- /dev/null +++ b/spec/models/vote_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +RSpec.describe Vote, type: :model do + it {should belong_to(:user)} + it {should belong_to(:votable)} + it {should belong_to(:author).class_name('User')} +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 0000000..2d92944 --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,62 @@ +# 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' +require 'simplecov' +SimpleCov.start + + + +# 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 diff --git a/spec/support/shoulda_matcher.rb b/spec/support/shoulda_matcher.rb index e69de29..dc0c322 100644 --- a/spec/support/shoulda_matcher.rb +++ b/spec/support/shoulda_matcher.rb @@ -0,0 +1,6 @@ +Shoulda::Matchers.configure do |config| + config.integrate do |with| + with.test_framework :rspec + with.library :rails + end +end \ No newline at end of file