diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6e907a2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +### v0.1.0 +* Integrate rspec +* Add rake db:reset task +* Upgrade rake to 10.1.0 +* Upgrade sinatra to 1.4.3 +* Upgrade sinatra-contrib to 1.4.1 + +### v.0.1.1 +* Implement a basic class and spec to exercise it +* Relocate misplaced comma set +* Convert models/README to Markdown +* Correct misusue of e.g. +* Convert helpers/README to Markdown +* Implement a "em" helper for reference; improve README diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..8747fba --- /dev/null +++ b/Gemfile @@ -0,0 +1,30 @@ +source 'https://rubygems.org' +ruby '2.0.0' + +# PostgreSQL driver +gem 'pg' + +# Sinatra driver +gem 'sinatra' +gem 'sinatra-contrib' + +gem 'activesupport', '~>4.1' +gem 'activerecord', '~>4.1' + +gem 'rake' + +gem 'shotgun' + +gem 'bcrypt' +gem 'faker' + +group :test do + gem 'shoulda-matchers' + gem 'rack-test' + gem 'rspec' + gem 'capybara' +end + +group :test, :development do + gem 'factory_girl' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..9c4574a --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,94 @@ +GEM + remote: https://rubygems.org/ + specs: + activemodel (4.1.1) + activesupport (= 4.1.1) + builder (~> 3.1) + activerecord (4.1.1) + activemodel (= 4.1.1) + activesupport (= 4.1.1) + arel (~> 5.0.0) + activesupport (4.1.1) + i18n (~> 0.6, >= 0.6.9) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.1) + tzinfo (~> 1.1) + arel (5.0.1.20140414130214) + backports (3.6.0) + bcrypt (3.1.10) + builder (3.2.2) + capybara (2.2.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + diff-lcs (1.2.5) + factory_girl (4.4.0) + activesupport (>= 3.0.0) + faker (1.3.0) + i18n (~> 0.5) + i18n (0.6.9) + json (1.8.1) + mime-types (2.3) + mini_portile (0.6.0) + minitest (5.3.4) + multi_json (1.10.1) + nokogiri (1.6.2.1) + mini_portile (= 0.6.0) + pg (0.17.1) + rack (1.5.2) + rack-protection (1.5.3) + rack + rack-test (0.6.2) + rack (>= 1.0) + rake (10.3.2) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.8) + rspec-expectations (2.14.5) + diff-lcs (>= 1.1.3, < 2.0) + rspec-mocks (2.14.6) + shotgun (0.9) + rack (>= 1.0) + shoulda-matchers (2.6.1) + activesupport (>= 3.0.0) + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + sinatra-contrib (1.4.2) + backports (>= 2.0) + multi_json + rack-protection + rack-test + sinatra (~> 1.4.0) + tilt (~> 1.3) + thread_safe (0.3.4) + tilt (1.4.1) + tzinfo (1.2.0) + thread_safe (~> 0.1) + xpath (2.0.0) + nokogiri (~> 1.3) + +PLATFORMS + ruby + +DEPENDENCIES + activerecord (~> 4.1) + activesupport (~> 4.1) + bcrypt + capybara + factory_girl + faker + pg + rack-test + rake + rspec + shotgun + shoulda-matchers + sinatra + sinatra-contrib diff --git a/README.md b/README.md index 48cc4d4..11a2945 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,24 @@ -This repo should be used to hold your phase 2, week 1 passion project. -see more about [portfolio challenges and passion projects here](../../../phase-2-guide/blob/sf/portfolio_challenges.md) + +### Quickstart + +1. `bundle install` +2. `bundle exec shotgun config.ru` +3. `bundle exec rake db:create` +4. `bundle exec rake db:migrate` +5. `bundle exec rake db:seed` +6. [Local host page](http://localhost:9393/) + +These tasks might not need `bundle exec` prepended to it + +### A website for community sharing of Car Costs + +1. Website has authentication and authorizations with login and registration +2. Website has a many to many relationship with Users, Prices and Cars. +3. Website uses activerecord CRUD to create, read, update and delete prices. +4. Website has validations and error handling. +5. Website uses html and css. +6. Website uses Sinatra for routing. + + +## by Andrew Yan \ No newline at end of file diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..a993c81 --- /dev/null +++ b/Rakefile @@ -0,0 +1,141 @@ +require 'rake' + +require ::File.expand_path('../config/environment', __FILE__) + +# Include all of ActiveSupport's core class extensions, e.g., String#camelize +require 'active_support/core_ext' + +namespace :generate do + desc "Create an empty model in app/models, e.g., rake generate:model NAME=User" + task :model do + unless ENV.has_key?('NAME') + raise "Must specificy model name, e.g., rake generate:model NAME=User" + end + + model_name = ENV['NAME'].camelize + model_filename = ENV['NAME'].underscore + '.rb' + model_path = APP_ROOT.join('app', 'models', model_filename) + + if File.exist?(model_path) + raise "ERROR: Model file '#{model_path}' already exists" + end + + puts "Creating #{model_path}" + File.open(model_path, 'w+') do |f| + f.write(<<-EOF.strip_heredoc) + class #{model_name} < ActiveRecord::Base + # Remember to create a migration! + end + EOF + end + end + + desc "Create an empty migration in db/migrate, e.g., rake generate:migration NAME=create_tasks" + task :migration do + unless ENV.has_key?('NAME') + raise "Must specificy migration name, e.g., rake generate:migration NAME=create_tasks" + end + + name = ENV['NAME'].camelize + filename = "%s_%s.rb" % [Time.now.strftime('%Y%m%d%H%M%S'), ENV['NAME'].underscore] + path = APP_ROOT.join('db', 'migrate', filename) + + if File.exist?(path) + raise "ERROR: File '#{path}' already exists" + end + + puts "Creating #{path}" + File.open(path, 'w+') do |f| + f.write(<<-EOF.strip_heredoc) + class #{name} < ActiveRecord::Migration + def change + end + end + EOF + end + end + + desc "Create an empty model spec in spec, e.g., rake generate:spec NAME=user" + task :spec do + unless ENV.has_key?('NAME') + raise "Must specificy migration name, e.g., rake generate:spec NAME=user" + end + + name = ENV['NAME'].camelize + filename = "%s_spec.rb" % ENV['NAME'].underscore + path = APP_ROOT.join('spec', filename) + + if File.exist?(path) + raise "ERROR: File '#{path}' already exists" + end + + puts "Creating #{path}" + File.open(path, 'w+') do |f| + f.write(<<-EOF.strip_heredoc) + require 'spec_helper' + + describe #{name} do + pending "add some examples to (or delete) #{__FILE__}" + end + EOF + end + end + +end + +namespace :db do + desc "Drop, create, and migrate the database" + task :reset => [:drop, :create, :migrate] + + desc "Create the databases at #{DB_NAME}" + task :create do + puts "Creating development and test databases if they don't exist..." + system("createdb #{APP_NAME}_development && createdb #{APP_NAME}_test") + end + + desc "Drop the database at #{DB_NAME}" + task :drop do + puts "Dropping development and test databases..." + system("dropdb #{APP_NAME}_development && dropdb #{APP_NAME}_test") + end + + desc "Migrate the database (options: VERSION=x, VERBOSE=false, SCOPE=blog)." + task :migrate do + ActiveRecord::Migrator.migrations_paths << File.dirname(__FILE__) + 'db/migrate' + ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true + ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration| + ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope) + end + end + + desc "rollback your migration--use STEP=number to step back multiple times" + task :rollback do + step = (ENV['STEP'] || 1).to_i + ActiveRecord::Migrator.rollback('db/migrate', step) + Rake::Task['db:version'].invoke if Rake::Task['db:version'] + end + + desc "Populate the database with dummy data by running db/seeds.rb" + task :seed do + require APP_ROOT.join('db', 'seeds.rb') + end + + desc "Returns the current schema version number" + task :version do + puts "Current version: #{ActiveRecord::Migrator.current_version}" + end + + namespace :test do + desc "Migrate test database" + task :prepare do + system "rake db:migrate RACK_ENV=test" + end + end +end + +desc 'Start IRB with application environment loaded' +task "console" do + exec "irb -r./config/environment" +end + +task :default => :spec diff --git a/app/controllers/.gitkeep b/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/index.rb b/app/controllers/index.rb new file mode 100644 index 0000000..46dfda7 --- /dev/null +++ b/app/controllers/index.rb @@ -0,0 +1,164 @@ +#TODO: -stretch goal- redirect back to previous page if you go to login or register. + +#User.cars or User.prices gets cars or prices for that instance of user. + + +get '/' do + redirect '/cars' +end + +get '/login' do + redirect '/cars' if logged_in? + + erb :login +end + +#authenticates login +post '/login' do + @email = params[:email] + @password = params[:password] + @user = user_at_email(params[:email]) + + if fields_not_blank?( @email, @password ) + if user_is_authenticated?( @user, @password ) + session_set_current_user(@user) + redirect '/cars' + else + @login_error = "The email address or password you’ve entered is not valid." + erb :login + end + else + @login_error = "Please enter all required fields" + erb :login + end +end + +# display register page +get '/register' do + @user = nil + redirect '/cars' if logged_in? + erb :register +end + +# goes to homepage or back to register page +post '/register' do + @name = params[:name] + @email = params[:email] + @password = params[:password] + if any_blank_fields?( @email, @password, @name) + @errors = "Please enter all required fields" + erb :register + else + @user = User.create(name: @name, email: @email, password: @password) + if @user.errors[:email].any? + @error_email = @user.errors[:email][0] + erb :register + else + redirect '/login' + end + end +end + +#logout +get '/logout' do + session_logout + redirect '/' +end + +get '/cars' do + erb :car +end + +get '/cars/:id/prices' do |id| +#shows page of prices for a specific car +@car = Car.find(id) +@id = id +erb :prices +end + +get '/cars/:car_id/prices/new' do |car_id| + # erb :new_price if logged_in? + # redirect '/login' + if logged_in? + @car = Car.find(car_id) + @id = car_id + erb :new_price + else + redirect '/login' + end +end + +#creates a new price for that car. +post '/cars/:car_id/prices/new' do |car_id| + redirect '/login' unless logged_in? + @price = Price.create({cost: params[:cost], user_id: session_current_user_id, car_id: car_id }) + if @price.errors[:cost].any? + @errors = @price.errors[:cost][0] + @car = Car.find(car_id) + @id = car_id + erb :new_price + else + redirect "/cars/#{car_id}/prices" + end + +end + +#show a specific price and who submitted it. +get '/cars/:car_id/prices/:price_id' do |car_id, price_id| + @car = Car.find(car_id) + @id = car_id + @price_id = price_id + @price = Price.find(price_id) + @user = @price.user + erb :price_id +end + + + +#display edit page specific price for car +get '/cars/:car_id/prices/:price_id/edit' do |car_id, price_id| + redirect '/login' unless logged_in? + @car = Car.find(car_id) + @id = car_id + @price_id = price_id + @price = Price.find(price_id) + @user = @price.user + + if @user != session[:current_user] + @access = false + @errors = "You're can only edit your submissions" + end + erb :edit +end + +put '/cars/:car_id/prices/:price_id' do |car_id, price_id| + @price = Price.find(price_id) + @price.cost = params[:cost] + @price.save + @user = @price.user + + if @price.valid? + redirect "/cars/#{car_id}/prices/#{price_id}" + else + @error_cost = @price.errors[:cost][0] + redirect "/cars/#{car_id}/prices/#{price_id}/edit" + end +end + +get '/cars/:car_id/prices/:price_id/delete' do |car_id, price_id| + @price = Price.find(price_id) + @car_id = car_id + erb :delete +end + + +#deletes a price you've created in the past +delete '/cars/:car_id/prices/:price_id' do |car_id, price_id| + @car_id = car_id + @price = Price.find(price_id) + @price.destroy + redirect "/cars/#{car_id}/prices" +end + + + diff --git a/app/helpers/.gitkeep b/app/helpers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/helpers/README.md b/app/helpers/README.md new file mode 100644 index 0000000..f3ef772 --- /dev/null +++ b/app/helpers/README.md @@ -0,0 +1,32 @@ +# HELPERS + +## Basics + +Helpers are methods that are defined in a central place but which can be used +inside of your view templates. An example of a helper is shown in +`app/views/index.erb` inside where we use the `em` helper. + +## Adding helpers + +Add helpers here inside of *.rb files inside this directory e.g.: +`app/helpers/formatting.rb`. + +Inside this file define your helpers like: + +``` +helpers do + def em(text) + "#{text}" + end +end +``` + +## Reloading + +After you create new files containing helpers, you must restart the web server +so that your changes are reloaded into the running web server process. This is +because helper files are read *only once* at web-server *startup* time. Also, +if you add a new helper or alter its implementation, you *also* must restart it +so that "sourced" (i.e. read in) anew. Other web frameworks do not require +this restart e.g. Rails but in this starter kit you're best not "bubbling out" +helpers until your implementation has "gelled." diff --git a/app/helpers/session_helper.rb b/app/helpers/session_helper.rb new file mode 100644 index 0000000..8218fde --- /dev/null +++ b/app/helpers/session_helper.rb @@ -0,0 +1,40 @@ +def user_at_email(email) + User.find_by_email(email) +end + +def user_is_authenticated?(user, password) + return false if user.blank? + user.authenticate(password) ? true : false +end + +def logged_in? + !!session[:current_user] +end + +def session_set_current_user(user) + session[:current_user] = user +end + +def session_current_user_id + session[:current_user].id +end + +def session_user_prices + session[:current_user].prices +end + +def session_logout + session.delete :current_user +end + +def fields_not_blank?(email,password,*name) + if !!name[0] + !email.blank? && !password.blank? && !name[0].blank? + else + !email.blank? && !password.blank? + end +end + +def any_blank_fields?(email, password, name) + email.blank? || password.blank? || name.blank? +end \ No newline at end of file diff --git a/app/models/.gitkeep b/app/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/models/README.md b/app/models/README.md new file mode 100644 index 0000000..12849f7 --- /dev/null +++ b/app/models/README.md @@ -0,0 +1,5 @@ +Add your ActiveRecord models here. + +You can create them by being in the application root directory and running: + +`rake generate:model NAME=User` diff --git a/app/models/car.rb b/app/models/car.rb new file mode 100644 index 0000000..9cf20a7 --- /dev/null +++ b/app/models/car.rb @@ -0,0 +1,8 @@ +class Car < ActiveRecord::Base + has_many :prices + has_many :users, through: :prices + + validates :make, presence: true + validates :model, presence: true + validates :year, presence: true +end diff --git a/app/models/price.rb b/app/models/price.rb new file mode 100644 index 0000000..4c9bcba --- /dev/null +++ b/app/models/price.rb @@ -0,0 +1,7 @@ +class Price < ActiveRecord::Base + belongs_to :user + belongs_to :car +validates :cost, presence: {message: "cost can't be blank"} +validates :cost, format: { with: /\A[0-9]{2,}\z/, message: "Only numbers allowed and no commas" } +end + diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..4c64c5f --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,27 @@ +class User < ActiveRecord::Base + has_many :prices + has_many :cars, through: :prices + + validates :email, presence: true + validates :email, uniqueness: { message: "email has already been taken" } + validates :email, format: { with: /\A[\w]{1,}\.?[\w]{1,}@[a-zA-Z0-9]{2,}\.[a-zA-Z0-9]{2,}\z/, message: "Not a valid email" } + validates :name, presence: {message: "Must have a name"} + validates :password_hash, presence: true + +include BCrypt + + def password + @password ||= Password.new(password_hash) + end + + def password=(new_password) + @password = Password.create(new_password) + self.password_hash = @password + end + + def authenticate(password) + self.password == password + end + + +end diff --git a/app/views/.gitkeep b/app/views/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/views/car.erb b/app/views/car.erb new file mode 100644 index 0000000..178dbd4 --- /dev/null +++ b/app/views/car.erb @@ -0,0 +1,12 @@ +
+

Cars Makes

+ +
diff --git a/app/views/delete.erb b/app/views/delete.erb new file mode 100644 index 0000000..5d12360 --- /dev/null +++ b/app/views/delete.erb @@ -0,0 +1,10 @@ +<% if !!@price && @price.user == session[:current_user] %> +

Are you sure you want to delete the following price submission: "$<%= @price.cost %>"?

+
+ + + Cancel +
+<% else %> +

Price not found / you're not authorized to delete this.

+<% end %> \ No newline at end of file diff --git a/app/views/edit.erb b/app/views/edit.erb new file mode 100644 index 0000000..a91a599 --- /dev/null +++ b/app/views/edit.erb @@ -0,0 +1,25 @@ +

<%= @car.year %> <%= @car.make %>

+ +

<%= @car.model %>

+ Add your own price +
+<% if @user != session[:current_user] %> +
+

<%= @errors %>

+
+<% else %> +
+
Edit price:
+
+ + + + <% if !!@error_cost %> +
+

<%= @error_cost %>

+
+ <% end %> +
+
+
Delete Your Submission
+ <% end %> \ No newline at end of file diff --git a/app/views/index.erb b/app/views/index.erb new file mode 100644 index 0000000..322d703 --- /dev/null +++ b/app/views/index.erb @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/app/views/layout.erb b/app/views/layout.erb new file mode 100644 index 0000000..9a3c9a0 --- /dev/null +++ b/app/views/layout.erb @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + +
+
+

Car Price Finder

+

find how much people paid...

+
+
+ +
+
+
  • View Car Prices
  • +
    + + + <%= yield %> + + diff --git a/app/views/login.erb b/app/views/login.erb new file mode 100644 index 0000000..5b3c582 --- /dev/null +++ b/app/views/login.erb @@ -0,0 +1,14 @@ +
    + Sign In + <% if !!@login_error %> +
    +

    <%= @login_error %>

    +
    + <% end %> +
    + + + +
    +
    + diff --git a/app/views/new_price.erb b/app/views/new_price.erb new file mode 100644 index 0000000..a99f039 --- /dev/null +++ b/app/views/new_price.erb @@ -0,0 +1,16 @@ +

    <%= @car.year %> <%= @car.make %>

    +

    <%= @car.model %>

    + Add your own price +
    +
    +
    New Price:
    +
    + + + <% if !!@errors %> +
    +

    <%= @errors %>

    +
    + <% end %> +
    +
    diff --git a/app/views/price_id.erb b/app/views/price_id.erb new file mode 100644 index 0000000..044eace --- /dev/null +++ b/app/views/price_id.erb @@ -0,0 +1,13 @@ +

    <%= @car.year %> <%= @car.make %>

    +

    <%= @car.model %>

    +
    +
    + +

    $<%= @price.cost %>

    + +

    + edit +

    + +

    Submitted by <%= @user.name %>

    +
    diff --git a/app/views/prices.erb b/app/views/prices.erb new file mode 100644 index 0000000..0ce652c --- /dev/null +++ b/app/views/prices.erb @@ -0,0 +1,18 @@ +

    <%= @car.year %> <%= @car.make %>

    +

    <%= @car.model %>

    + Add your own price +
    +
    + + <% @car.prices.each do |price| %> + + <% end %> +
    diff --git a/app/views/register.erb b/app/views/register.erb new file mode 100644 index 0000000..76af3b1 --- /dev/null +++ b/app/views/register.erb @@ -0,0 +1,32 @@ +
    + Register + <% if !!@errors %> +
    +

    <%= @errors %>

    +
    + <% end %> + +
    +
    +
    + +
    +
    + +
    + <% if !!@error_email %> +
    +

    <%= @error_email %>

    +
    + <% end %> + +
    + +
    +
    + +
    +
    +
    +
    + diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..6036b25 --- /dev/null +++ b/config.ru @@ -0,0 +1,6 @@ +# Require config/environment.rb +require ::File.expand_path('../config/environment', __FILE__) + +set :app_file, __FILE__ + +run Sinatra::Application diff --git a/config/database.rb b/config/database.rb new file mode 100644 index 0000000..08c74b7 --- /dev/null +++ b/config/database.rb @@ -0,0 +1,42 @@ +# Log queries to STDOUT in development +if Sinatra::Application.development? + ActiveRecord::Base.logger = Logger.new(STDOUT) +end + +# Automatically load every file in APP_ROOT/app/models/*.rb, e.g., +# autoload "Person", 'app/models/person.rb' +# +# We have to do this in case we have models that inherit from each other. +# If model Student inherits from model Person and app/models/student.rb is +# required first, it will throw an error saying "Person" is undefined. +# +# With this lazy-loading technique, Ruby will try to load app/models/person.rb +# the first time it sees "Person" and will only throw an exception if +# that file doesn't define the Person class. +# +# See http://www.rubyinside.com/ruby-techniques-revealed-autoload-1652.html +Dir[APP_ROOT.join('app', 'models', '*.rb')].each do |model_file| + filename = File.basename(model_file).gsub('.rb', '') + autoload ActiveSupport::Inflector.camelize(filename), model_file +end + +# Heroku controls what database we connect to by setting the DATABASE_URL environment variable +# We need to respect that if we want our Sinatra apps to run on Heroku without modification +db = URI.parse(ENV['DATABASE_URL'] || "postgres://localhost/#{APP_NAME}_#{Sinatra::Application.environment}") + +DB_NAME = db.path[1..-1] + +# Note: +# Sinatra::Application.environment is set to the value of ENV['RACK_ENV'] +# if ENV['RACK_ENV'] is set. If ENV['RACK_ENV'] is not set, it defaults +# to :development + +ActiveRecord::Base.establish_connection( + :adapter => db.scheme == 'postgres' ? 'postgresql' : db.scheme, + :host => db.host, + :port => db.port, + :username => db.user, + :password => db.password, + :database => DB_NAME, + :encoding => 'utf8' +) diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..2e04cae --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,47 @@ +# Set up gems listed in the Gemfile. +# See: http://gembundler.com/bundler_setup.html +# http://stackoverflow.com/questions/7243486/why-do-you-need-require-bundler-setup +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) + +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) + +# Require gems we care about +require 'rubygems' + +require 'uri' +require 'pathname' + +require 'pg' +require 'active_record' +require 'logger' + +require 'sinatra' +require "sinatra/reloader" if development? + +require 'erb' +require 'faker' +require 'bcrypt' + +# Some helper constants for path-centric logic +APP_ROOT = Pathname.new(File.expand_path('../../', __FILE__)) + +APP_NAME = APP_ROOT.basename.to_s + +configure do + # By default, Sinatra assumes that the root is the file that calls the configure block. + # Since this is not the case for us, we set it manually. + set :root, APP_ROOT.to_path + # See: http://www.sinatrarb.com/faq.html#sessions + enable :sessions + set :session_secret, ENV['SESSION_SECRET'] || 'this is a secret shhhhh' + + # Set the views to + set :views, File.join(Sinatra::Application.root, "app", "views") +end + +# Set up the controllers and helpers +Dir[APP_ROOT.join('app', 'controllers', '*.rb')].each { |file| require file } +Dir[APP_ROOT.join('app', 'helpers', '*.rb')].each { |file| require file } + +# Set up the database and models +require APP_ROOT.join('config', 'database') diff --git a/db/migrate/.gitkeep b/db/migrate/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/db/migrate/20150206080134_create_users.rb b/db/migrate/20150206080134_create_users.rb new file mode 100644 index 0000000..7e58201 --- /dev/null +++ b/db/migrate/20150206080134_create_users.rb @@ -0,0 +1,12 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + + t.string :name + t.string :email + t.string :password_hash + + t.timestamps + end + end +end diff --git a/db/migrate/20150206080139_create_cars.rb b/db/migrate/20150206080139_create_cars.rb new file mode 100644 index 0000000..8f5ae64 --- /dev/null +++ b/db/migrate/20150206080139_create_cars.rb @@ -0,0 +1,11 @@ +class CreateCars < ActiveRecord::Migration + def change + create_table :cars do |t| + t.string :make + t.string :model + t.string :year + + t.timestamps + end + end +end diff --git a/db/migrate/20150206080223_create_prices.rb b/db/migrate/20150206080223_create_prices.rb new file mode 100644 index 0000000..dbe16f5 --- /dev/null +++ b/db/migrate/20150206080223_create_prices.rb @@ -0,0 +1,12 @@ +class CreatePrices < ActiveRecord::Migration + def change + create_table :prices do |t| + t.string :cost + + t.integer :car_id + t.integer :user_id + + t.timestamps + end + end +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 0000000..1e8cdce --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,47 @@ +cars = { + "Aston Martin" => ["DB9"], + "BMW" => ["328i", "M3", "M6", "i8", "i3"], + "Chevrolet" => ["Corvette Z06"], + "Dodge" => ["Challenger"], + "Ford" => ["Focus", "Flex", "Escape", "Fusion"], + "Ferrari" => ["California", "458Italia"], + "Honda" => ["Fit EX", "Accord EX"], + "Jaguar" => ["F-Type R"], + "Mercedes" => ["SLS-Class SLS AMG GT Final Edition"], + "Nissan" => ["370Z", "Altima"], + "Subaru" => ["BRZ", "WRX"], + "Tesla" => ["Model S"], + "Toyota" => ["FRS"], + "Volkwagen" => ["GTI"] +} + +years = (2008..2015).to_a + +cars.each do |key, models| + models.each do |model| + Car.create({ make: key, model: model, year: years.sample }) + end +end + +user = (1..21).to_a +car = (1..25).to_a + +User.create({name: "Andrew Yan", email: "yandrew@gmail.com", password: "1"}) +Price.create({ cost: 200000,user_id: 1, car_id: 1}) + +20.times do + User.create({name: Faker::Name.name, email: Faker::Internet.email, password:1 }) +end + +def fake_cost + (10000 + rand(50000)).to_s +end + +60.times do + Price.create({cost: fake_cost, user_id: user.sample, car_id: car.sample}) +end + + + + + diff --git a/public/css/application.css b/public/css/application.css new file mode 100644 index 0000000..f0e0ce8 --- /dev/null +++ b/public/css/application.css @@ -0,0 +1,235 @@ +body { + color: rgb(75, 147, 130); + background-color: #5B6F72; +} + + +.logins { + + position: absolute; + right: 20px; + bottom: 5px; +} + + + +a { + color: rgb(242, 240, 236); +} + +a:hover { + color: rgb(160, 33, 95); +} + + +div { + +} + +ul { + +} + +li { + +} + +a { + text-decoration: none; + color: rgb(242, 240, 236); +} + +a :visited { + text-decoration: none; +} + +a :hover { + text-decoration: none; +} + +span { + +} + +input { + line-height: 2em; + margin: 8px 5px; + border-radius: 5px; + background-color: azure; +} + +input[type="submit"] { + width: 130px; + border-radius: 5px; + background-color: azure; +} + +input:hover { + border-style:2px solid rgb(57, 84, 168); +} + +div.center-frame h4{ + color: rgb(228, 179, 104); + font-size: 1.2em; +} + +div.error{ + color: red; + font-size: 1em; +} + +div.form-price-add h5 { + color: rgb(232, 145, 73); + font-size: 1.3em; + text-align: center; +} + +div.home_title { + height: 500px; + + position: inherit; + text-align: center; +} + +div.form-price-add { + text-align: center; +} + +div.price-title a{ + font-size: 1em; +} +div.title { + position: relative; + display: initial; + text-align: center; +} + +div.home_title h1 a { + text-align: center; + color: #6D91CF; +} + +div.home_title h4 { + color: #5291A6; +} + +span.signin { + color: rgb(216, 134, 54); + font-size: 1.5em; +} + +span.error-message { + color: rgb(218, 36, 36); + font-size: 1em; +} + +ul.header-list { + position: relative; +} + +ul.header-list li { + list-style: none; + display: table-cell; + padding: 5px 15px; +} + + + +.top_container { + position: relative; + top: 0px; + height: 150px; + width: 100%; + margin: 0 auto; + background-color: #AAA9B3; + display: inline-block; +} + +.center-frame{ + left: 20%; + position: absolute; +} +.car-price { + list-style: none; + position: absolute; + bottom: 25px; + left: 30px; +} + +.error-message { + color: red; +} +ul.make li.make-li { + color: rgb(136, 7, 7); + list-style: none; + margin: 5px auto; + fort-size: 1.2em; +} + +div.price-title a { + font-size: 1em; +} + +.price-list { + text-align: left; + width: 40%; + margin: 10px auto; +} + +.price-title h4 { + color: rgb(136, 7, 7); + font-size: 2em; + text-align: center; +} + +.price-title { + text-align: center; +} + +.registration-form { + border: 2px solid white; + text-align: center; + margin: 60px auto; + padding: 23px; + width: 50%; + border-radius: 24px; + background-color: rgb(77, 120, 121); + position: relative; +} + +span.star-marker { + color: red; +} + +.subtitle { + font-size: medium + color: rgb(34, 123, 178) +} + +.sign-in-form { + border: 2px solid white; + text-align: center; + margin: 50px auto; + padding: 23px; + width: 66%; + border-radius: 24px; + background-color: rgb(77, 120, 121); +} + +.form-price-add { + border: 2px solid white; + text-align: center; + margin: 50px auto; + padding: 23px; + width: 45%; + border-radius: 24px; + background-color: rgb(77, 120, 121); +} + + + + + + + + diff --git a/public/css/normalize.css b/public/css/normalize.css new file mode 100644 index 0000000..196d223 --- /dev/null +++ b/public/css/normalize.css @@ -0,0 +1,423 @@ +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined in IE 8/9. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9. + * Hide the `template` element in IE, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9, Safari 5, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari 5, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari 5 and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari 5, and Chrome. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9. + */ + +img { + border: 0; +} + +/** + * Correct overflow displayed oddly in IE 9. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari 5. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari 5, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8+, and Opera + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..fab98c7 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/js/.gitkeep b/public/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/public/js/application.js b/public/js/application.js new file mode 100644 index 0000000..52e01e6 --- /dev/null +++ b/public/js/application.js @@ -0,0 +1,7 @@ +$(document).ready(function() { + // This is called after the document has loaded in its entirety + // This guarantees that any elements we bind to will exist on the page + // when we try to bind to them + + // See: http://docs.jquery.com/Tutorials:Introducing_$(document).ready() +}); diff --git a/spec/controller_index_spec.rb b/spec/controller_index_spec.rb new file mode 100644 index 0000000..170c13e --- /dev/null +++ b/spec/controller_index_spec.rb @@ -0,0 +1,12 @@ +require_relative 'spec_helper' + + +describe "Relationships" do + it "tests for users prices" do + # get '/' + test_user = User.find(1) + test_user.prices[0].cost.to eq(200000) + + end +end + diff --git a/spec/features/.gitkeep b/spec/features/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..eb9c7d7 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,22 @@ +require 'rubygems' + +# All our specs should require 'spec_helper' (this file) + +# If RACK_ENV isn't set, set it to 'test'. Sinatra defaults to development, +# so we have to override that unless we want to set RACK_ENV=test from the +# command line when we run rake spec. That's tedious, so do it here. +ENV['RACK_ENV'] ||= 'test' + +require File.expand_path("../../config/environment", __FILE__) +require 'shoulda-matchers' +require 'rack/test' +require 'capybara' +require 'capybara/rspec' + +RSpec.configure do |config| + config.include Rack::Test::Methods +end + +def app + Sinatra::Application +end