Skip to content

ga-chicago/wdi-cc4-sinatra-fullstack-lab-hw

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

3 Commits
Β 
Β 

Repository files navigation

ga_chi

wdi-11-chi curious-turtles

Full Stack Sinatra with ActiveRecord, Sessions, and Bcrypt

Follow each step of this lab. For each step, try to do it yourself first and see if you can get it to work. If you're stumped, click the arrow by "Details" to show the hidden code (works best when this file is viewed on github).

For example:

#this is an example
def test
	"asdf"
end

Whether or not you use the provided code, TYPE ALL THE CODE FOR THIS LESSON OUT YOURSELF. DO NOT PASTE. THIS WILL HELP YOU GET USED TO IT.

For a few sections, generally ones with new information, the code is not hidden.

1. Set up the files for a Sinatra app.

Gemfile

source 'https://rubygems.org'

gem 'pg'
gem 'sinatra'
gem 'sinatra-activerecord'
gem 'json'
gem 'pry'
gem 'bcrypt'

Since we added some gems, we bundle. Take a look at the Gemfile.lock that is generated.

$ bundle
$ less Gemfile.lock

πŸ”΄ Commit: "Gemfile + bundle"


2. Config.ru

Set up the minimum config.ru that you would need to get a modular Sinatra app to run.

config.ru

require 'sinatra/base'

# controllers
require './controllers/ApplicationController'

# routes
map('/') {
	run ApplicationController
}

3. Application controller

Create a barebones ApplicationController. What does that inherit from? Give it a '/' default get route.

controllers/ApplicationController.rb

class ApplicationController < Sinatra::Base

	require 'bundler'
	Bundler.require()

	get '/' do
		"hey cool the server runs"
	end

end

Make sure your app runs.

$ bundle exec rackup

If so then....


πŸ”΄ Commit: "Basic server runs. ApplicationController + config.ru"


4. Render a view

Create a basic erb template that has an h1 with the name of your app. Something like "Awesome to do list App!."

β–ͺ️ Link up the views folder

controllers/ApplicationController.rb, after bundle stuff, before the route

set :views, File.expand_path('../views', File.dirname(__FILE__))

β–ͺ️ Create the view

views/hello.erb

<!DOCTYPE html>
<html>
<head>
	<title></title>
</head>
<body>
	<h1 style="border: 1px solid blue; border-radius: 10px">Awesome Item App (this h1 is in hello.erb)</h1>
</body>
</html>

β–ͺ️ Render the view in your default get route

controllers/ApplicationController.rb

get '/' do
	erb :hello
end

If it renders, then...


πŸ”΄ Commit: "set up views/rendered a template"


5. Add ActiveRecord, set up migrations.sql and seeds.sql.

For section 5, the code is not hidden.

β–ͺ️ Create a file, db/migrations.sql to represent our database structure. Write out the SQL below EXACTLY. Do not change any capitalizations, punctuation, singular/plural, or table/column names. Notice how we are describing relations between the tables--specifically user_id REFERENCES users(id)--that is how we know that a User has Items. That's the "relation"... we say "A User has many items." Incidentally, we also say an item belongs to a user. This is not just a PSQL or ActiveRecord thingβ€”that's how you generally describe relationships in your data. When you're done typing it out, open psql. Run DROP DATABASE item; then copy/paste the contents of the file.

db/migrations.sql

CREATE DATABASE item;

\c item

CREATE TABLE users(
  id SERIAL PRIMARY KEY,
  username VARCHAR(32),
  password VARCHAR(60)
);

CREATE TABLE items(
  id SERIAL PRIMARY KEY,
  title VARCHAR(255),
  user_id INT REFERENCES users(id)
);

β–ͺ️ Check that it was successful by

  • reading every line of output carefully from the psql terminal after you paste, and
  • checking out your tables: \d users and \d items

Similarly, you can create a seeds.sql file you can use to insert a lot of data all at once. This is helpful if you need to clear or reset or move your database, or when you deploy an app, or when you clone an existing project, and need some data to work with during development.

β–ͺ️ Create a file db/seeds.sql with SQL for a user with your name (lol) and password.

db/seeds.sql

INSERT INTO users (username, password) VALUES ('reuben', '12345');

Yes, there is a way to automate the migrations/seeds process in both Sinatra AND Rails. No, people don't always just paste it, but we will be for the remainder of this unit.

β–ͺ️ Require ActiveRecord in config.ru.

config.ru, after sinatra/base and before controllers:

require 'sinatra/activerecord'

β–ͺ️ Add the code to connect to an 'item' database in ApplicationController. Database name here must match database name in your migrations.sql file.

controllers/ApplicationController.rb, after bundle stuff and before setting views folder:

	ActiveRecord::Base.establish_connection(
 		:adapter => 'postgresql', 
 		:database => 'item'
	)

If you are able to start your server...


πŸ”΄ Commit: "added ActiveRecord and migrations/seeds"


6. Set up partials

Since we're about to have several different views, but still want to have a lot of things consistent across our whole site, we're going to use partials.

β–ͺ️ Create a layout.erb template. Cut all of the code from hello.erb and paste it into layout.erb. In hello.erb, which should now be empty, you'll just have an h2 saying "this is the hello template":

views/hello.erb

<h2 style="border: 1px solid brown; border-radius: 5px">This h2 is in the hello.erb<h2>

> We're using inline styles here to point out what divs are coming from what partials. DO NOT USE INLINE STYLES IN YOUR APPS.

β–ͺ️ Add the yield to the body in layout.erb, below the <h1>

views/layout.erb, in the <body>, below the <h1>

<%= yield %>

β–ͺ️ For fun, and so users can have a meaningful browser history, let's send a page title from the route. Change the <title> to include instance variable for the page name.

views/layout.erb

<title><%= @page %></title>

If you want you could also throw some text in layout.erb to help yourself remember where which text is coming from.

  <small>Thanks for using this site. This "footer" is in the main
  layout.erb and will appear on all pages. Β© 2018 no one.</small>

β–ͺ️ ...and then set the page title as an instance variable in the route

controllers/ApplicationController.rb

 	get '/' do
		@page = "hello"
  		erb :hello
  	end

πŸ”΄ Commit: "set up partials"


7. Item Controller

Add an Item controller with a dummy '/' index/get route and an "add item" route that renders an add item template (that we will create in a second). Don't forget to require and map it.

controllers/ItemController.rb

class ItemController < ApplicationController

	# index route
	get '/' do
		"this is get route in ItemController"
	end

	# add route 
	get '/add' do
		erb :add_item # this view will be created in the next step
	end

end

config.ru

require './controllers/ItemController'

...and farther down...

map('/items') { 
	run ItemController
}

If you can view '/items' then...


πŸ”΄ Commit: "Item Controller"


8. Go nuts with partials and variables

Create a partial form.erb with a form that has a red border that posts to @action and a method of @method where the text field has a value of @value and a placeholder of @placeholder and the button has a value of @buttontext.

views/form.erb

<form style="border: 1px solid red; border-radius: 4px;" action="<%= @action %>" method="<%= @method %>">
 	<input type="text" name="title" value='<%= @value %>' placeholder='<%= @placeholder %>'/>
 	<input type="submit" name="Submit Button" value="<%= @buttontext %>" />
 	<p>everything in this red box is in the form partial<p>
</form> 

Dabbling with nested partials: you can render a partial within a partial:

Create the add_item partial mentioned in the comment in the previous step. It should have only an <h2> with the name of the page, and then below that, the following line:

<%= erb(:form) %>

Edit the Item add route to send along all the proper values for the instance variables in form.erb and layout.erb and make the post to an as yet nonexistent '/items' post route.

controllers/ItemController.rb, in the get '/add' route, before the return:

	@page = "Add Item"
	@action = "/items"
	@method = "POST"
	@placeholder = "Enter your item!"
	@value=""
	@buttontext = "Add Item"

This is perhaps a bit overkill...the purpose is just to demonstrate. But make sure you have text on your button and in your input placeholder and page title. And make sure that when you click the button, it tries to POST to /items.


πŸ”΄ Commit: "Nested form partial"


9. Items dummy post route

When you clicked that button, turns out Sinatra didn't know that ditty (i.e. you haven't defined post '/' do ...etc...), so let's teach it to Sinatra. Make an item create route that prints the form data to the terminal and just sends back: "you posted. check your terminal." Try to post something and make sure your data looks right in the terminal.

controllers/ItemController.rb

# create route
  post '/add' do
    # params are in a hash called params, check your terminal
    # extra puts statements help you find this output amongst the very verbose terminal output
    puts "HERE IS THE PARAMS---------------------------------------"
    pp params
    puts "---------------------------------------------------------"
    "you posted. check your terminal."
  end


πŸ”΄ Commit: "Items post route"


10. Make items create route use ActiveRecord to actually insert in your db.

First you will need to create your model. Don't ya just love how easy it is!?

models/ItemModel.rb

class Item < ActiveRecord::Base

end 

Yep, that's it. Now see if you can figure out/google how to make your items create route do a simple insert of one item into a table with ActiveRecord. Again, it's absurdly simple. We won't worry about users until we have full CRUD for items, so for now, just hard-code the user_id to be 1. After you save, send the item back as JSON.

controllers/ItemController.rb:

post '/' do

	pp params

	# this is how you add something with ActiveRecord.  
	@item = Item.new
	@item.title = params[:title]
	@item.user_id = 1 # for now
	@item.save

	# hey there's a .to_json method. cool.
	@item.to_json

end

Now try to use it. Try to add some items with your form.

Uh oh, it's broke, ain't it?

Would be cool if it worked, but you likely get an error like "uninitialized constant Item...." What's that all about? What do you think that means. You actually read error messages right? What did we forget? Think about it! See if you can fix it!

config.ru, after controllers

#models
require './models/ItemModel'

Make sure it works by using psql to see what's in your items table. And check out your terminal where you have bundle exec rackup running, and see the SQL that ActiveRecord is writing for you (pretttyyyy colllorrrrss). Also, notice that in the browser, you can see in the JSON that an ID has been added. That's from the database and it means your insert was successful.

in your postgres CLI:

\c item;
SELECT * FROM items;

If you got JSON in the browser, and data in your items table and it all looks right...


πŸ”΄ Commit: "Item create route saves data using ActiveRecord"


11. Create an item index page.

3 steps:

  • update item index route get all items with ActiveRecord before you render an :item_index template (that you're about to make). Code is very simple. Try guessing/googling. Just send back JSON while you're figuring it out. Then once you get it and it's done...
  • create the :item_index partial that includes an "item list" <h2> and then iterates over @items to build a <ul> of <li>s. Render that template from index route.
  • When you have a working index page, update the item create (post) route to redirect to that index page after it does the insert

controllers/ItemController.rb

	# index route
 	get '/' do
		@items = Item.all # beautiful isn't it
		# @items.to_json
		@page = "Index of items"
		erb :item_index
  	end

views/item_index.erb

<h2 style="border: 1px solid purple; border-radius: 5px">Item index</h2>
<ul>
	<% @items.each do |item| %>
		<li><%= item.title %></li>
	<% end %>
</ul> 

controllers/ItemController.rb (again, but in the post '/' route this time)

		# @item.to_json # we will come back to this
		redirect '/items'

Again, look at the terminal to see the SQL that's being written for you.


πŸ”΄ Commit "Item index page"


12. Since we have multiple pages, let's make a <nav> real quick.

Add a nav. If you want you can also add a site-wide title.

views/layout.erb:

  <nav>
    <!-- REMEMBER: DO NOT USE INLINE STYLES.  -->
    <p style="display: inline-block;">Nav:</p>
    <a href="/items">Item list</a> β€’
    <a href="/items/add">Add Items</a>
  </nav>
  
  <h1 id="app-name">Awesome Site!</h1> 

(also, now, if you want, you can remove the footer, whose only purpose was to have some viewable content coming from the layout.erb container template)


πŸ”΄ Commit: "Added a nav"


13. Delete Functionality

3 steps:

  • Add the MethodOverride middleware (sound familiar??) (also, shown below)
  • Make each <li> in your index a form. The form will DELETE by POSTing and including a parameter _method set to DELETE, similarly to Express. However, this time, use <input type='hidden'> to do it instead of adding it in the query string. How will you know it's working? (Click below to show answer)
(when you click one of the delete buttons, "...doesn't know this ditty" should be telling you that you need to add a delete route....)
  • So then go ahead and write the item delete route. See if you can figure out/google how to do it. Again, ActiveRecord--very simple. You could probably get it just by guessing. Remember to redirect to index so user can see that the delete was successful.

controllers/ApplicationController.rb

	use Rack::MethodOverride  # we "use" middleware in Rack-based libraries/frameworks
	set :method_override, true

views/item_index.erb

<ul>
	<% @items.each do |item| %>
		<form action="/items/<%= item.id %>" method="POST">
		<input type="hidden" name="_method" value="DELETE" />
		<li><%= item.title %></li> 
		<button>Delete</button>
		</form>
	<% end %>
</ul> 

controllers/ItemController.rb

	delete '/:id' do
		# there are many ways to do this find statement, this is just one
		# remember you can play around with ActiveRecord by adding binding.pry 
		# and trying stuff out
		@item = Item.find params[:id]
		@item.destroy
		redirect '/items'
	end

Again, look at the SQL that's being generated for you. If you can delete, then....


πŸ”΄ Commit: "Item delete functionality"


14. Add a public folder for CSS and put all the CSS in it.

Add a public folder for CSS. Move all your styles there where they belong because since this isn't 1995, we separate content and presentation/formatting/layout/design.

Steps:

  • set it up on the server
  • link it up in the template
  • make a style to be sure it's working

controllers/ApplicationController.rb

set :public_dir, File.expand_path('../public', File.dirname(__FILE__))

views/layout.erb

<link rel="stylesheet" type="text/css" href="/css/style.css">

public/css/style.css

body {
	background-color: #f3d460;
}

Then put the styles from your html in your CSS and add classes for them, where necessary in your html. How you do all of this is up to you, click below to see an example (html omitted) if you like. You can delete the extra stuff like "everything in this red box", etc.

public/css/style.css

body {
	background-color: #f3d460;
}

/* from form.erb: be sure to add this class to that form while you're deleting the inline style */
.form-partial {
	border: 1px solid red; 
	border-radius: 4px; 
}

/* style the h1 in layout.erb */
h1#app-name {
	border: 1px solid blue; 
	border-radius: 10px;
}
nav p {
	display: inline-block;
}

/* from hello.erb and/or item_index.erb and/or add_item.erb */
h2 {
	border: 1px solid brown; 
	border-radius: 5px;
}


πŸ”΄ Commit: "Set up CSS public folder and moved CSS there"


15. Edit/Update

Try to create an edit functionality on your own. You have everything you need to do it at this point... you shouldn't need to google.

Don't forget: you can see the SQL that's being generated for you in your terminal.

  • First create the edit link, route, and view. Make sure it works. Don't forget to override the method.

views/item_index.erb

<a href="/items/edit/<%= item.id %>">(Edit)</a>

controllers/ItemController.rb

# edit route
get '/edit/:id' do
	@item = Item.find params[:id]
	@page = "Edit Item #{@item.id}" #why am i using interpolation here?  try with concatenation and see what happens.
	erb :edit_item
end

views/edit_item.erb

<h2>Edit Item <%= @item.id %></h2>

<form action="/items/<%= @item.id %>" method="POST">
	<input type="hidden" name="_method" value="PATCH" />
	<input type="text" size="75" name="title" placeholder="Enter new value for item <%= @item.id %>" value="<%= @item.title %>" />
	<button>Update Item</button>
</form>

  • Then create the update route and have it redirect to '/items'. Make sure it works. See the notes in the code below.

controllers/ItemController.rb

# update route
patch '/:id' do
	# like i said -- lots of ways to do this.  
	# http://api.rubyonrails.org/classes/ActiveRecord/FinderMethods.html
	# http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
	@items = Item.where(id: params[:id]) 

	# note: .where method gives us an array (Why?). So we must index. 
	# Might there have been a more appropriate query method to use 
	# instead of .where ?
	@item = @items[0]

	@item.title = params[:title]
	@item.save
	redirect '/items'
end

πŸ”΄ Commit: "Update functionality. Full CRUD achieved."


Do you see why people say Ruby and Sinatra (or rails) and a good ORM let you build application prototypes very quickly????????

Sessions in Sinatra

16. Enable sessions, and use it for messaging

We're eventually going to track if the user is logged in with sessions. But first, let's do a sessions exercise -- create a messaging functionality.

Enable sessions:

controllers/ApplicationController.rb

# after bundler stuff but before everything else: 

enable :sessions # yep, that's it

Set messages in routes:

controllers/ItemController.rb, create route

# after .save, before redirect:

session[:message] = "You added item \##{@item.id}."

controllers/ItemController.rb, update route

# after .save, before redirect:

session[:message] = "You updated item \##{@item.id}"

controllers/ItemController.rb, delete route

# after .delete, before redirect:

session[:message] = "You deleted item \##{@item.id}"

Create a style for the messages.

public/css/style.css

p.msg {
	background: lightgreen;
	color: brown;
	border: 1px solid black;
}

Display message in layout.erb. Remember to clear it from the session once it's been displayed.

views/layout.erb

<% if session[:message] %>
	<p class="msg">
	<%= session[:message] %>
	</p>
	<% session[:message] = nil
end %>

Pretty sweet, right?

Reminder: Anytime you're trying to figure out anything with sessions, you can pp session to see what's in there.


πŸ”΄ Commit: "Enabled sessions and used it to create messaging"


Login/register functionality.

For parts 17 through 22, we will build a login and register functionality. Less guidance is provided. Based on what you've already done, think about what is required to build this and see if you can do it. Again, you have everything you need, so basically no googling should be required. Maybe a quick look back at class notes? Just keep asking yourself "what am I trying to do right now?" and "where have I done something like this before that worked?". Don't forget to p or puts or print or pp things to make sure they're what you think they are, and don't forget that you can see any SQL that ActiveRecord wrote for you in your terminal.

17. Create a user model

models/UserModel.rb

class User < ActiveRecord::Base

end

config.ru

require './models/UserModel'

Remember: we already have users in our DB schema. Otherwise, we'd need to add that now.


πŸ”΄ Commit: "Added user model"


18. Make the parts: views and a controller with dummy routes.

Don't worry about making the login actually work yet, just make sure everything is set up first, as we've done above with items. Routes exist, and just send back JSON. NOTE: ALL CONTROLLERS (except ApplicationController) IN A SINATRA APP INHERIT FROM APPLICATION CONTROLLER.

views/register.erb

<h2>Sign up:</h2>
<form action="/user/register" method="POST">
	<input type="text" name="username" placeholder="desired username" /><br />
	<input type="password" name="password" placeholder="password"><br />
	<button>Register</button>	
</form>

views/login.erb

<h2>Log in below.</h2>

<h3>Need an account? Sign up <a href="/user/register">here</a></h3>

<form action="/user/login" method="POST">
	<input type="text" name="username" placeholder="username" /><br />
	<input type="password" name="password" placeholder="password"><br />
	<button>Login</button>	
</form>

controllers/UserController.rb

class UserController < ApplicationController

	get '/' do
		redirect '/user/login'
	end

	get '/login' do
		erb :login
	end

	get '/register' do
		erb :register
	end

	post '/login' do
		params.to_json
	end

	post '/register' do
		params.to_json
	end

end

config.ru

require './controllers/UserController'

and

map('/user') {
	run UserController
}

πŸ”΄ Commit: "User login/register templates, User controller with dummy routes (that just send back JSON)"


19. Make the post route for login actually do the login.

Remember to set session variables appropriately--is the user logged in? Once they've successfully logged in, tell them "logged in as...." using the messaging functionality you just built.

Hint: try .find_by

Ok we said don't google, but if you want to read about ActiveRecord .find_by method real quick, that's ok. Don't spend 45 minutes reading stackoverflow posts by confused people. Just read about the method in the the ActiveRecord Query methods docs for like 1 minute. But you can probably just use .find_by without even doing that.

controllers/UserController.rb

	post '/login' do
		@user = User.find_by(username: params[:username])

		if @user && @user.password == params[:password]
			session[:username] = @user.username
			session[:logged_in] = true
			session[:message] = "Logged in as #{@user.username}"
			redirect '/items'
		else
			session[:message] = "Invalid username or password."
			redirect '/user/login'
		end
	end


πŸ”΄ Commit: "Login works"


20. Make the register route work.

Remember session stuff.

Tip: Don't be rude to the user by forcing them to log in again after registering. Registering should automatically log them in.

controllers/UserController.rb

	post '/register' do
		@user = User.new
		@user.username = params[:username]
		@user.password = params[:password]
		@user.save
		session[:logged_in] = true
		session[:username] = @user.username
		session[:message] = "Thank you for registering (as #{@user.username}).  Enjoy the site!"
		redirect '/items'
	end


πŸ”΄ Commit: "Register works"


21. Add login info/links to nav.

When the user is logged in, it should say, at the top of the page "Logged in as (username)" and show a logout link.

If the user is not logged in, only a Log In link should be displayed there.

public/css/style.css

nav {
	display: flex;
	justify-content: space-between;
}

views/layout.erb

	<nav>
		<div>
			<p>Nav:</p>
			<a href="/items">Item list</a> β€’
			<a href="/items/add">Add Items</a>
		</div>
		<div>
			<% if !session[:logged_in] %>
				<a href="/user/login">Login</a>
			<% else %>
				<p>Logged in as <%= session[:username] %></p>
				<a href="/user/logout">Logout</a>
			<% end %>
		</div>
	</nav>


πŸ”΄ Commit: "Logged in as.... in nav"


22. Create a logout route

	get '/logout' do
		session[:username] = nil
		session[:logged_in] = false
		redirect '/user/login'
	end

πŸ”΄ Commit: "Logout works"


23. "You must be logged in to do that"

In Express we could have written router-level middleware like this at the top of our controller:

app.use((req, res, next) => {
	if(!session.loggedIn) {
		req.session.message = "You must be logged in to do that."
		res.redirect('/user/login');
	} else {
		next();
	}
}) 

Writing actual Middleware for Rack-based apps is a little more complex, so we will create a filter with before to achieve similar functionality.

controllers/ItemController.rb

# this is called a filter and will be run before all requests in this route
before do
	if !session[:logged_in]
		session[:message] = "You must be logged in to do that"
		redirect '/user/login'
	end
end

πŸ”΄ Commit: "You must be logged in to do that."


24. Track user_id in session

This is a critical step towards having multiple users!

  • In your login and register routes, set session[:user_id].
  • Remember to set it to back to nil in the logout route.
  • Update your item create route to use session[:user_id] for inserts instead of the 1 we hard-coded in during step 10.

controllers/UserController.rb

In the login route:

session[:user_id] = @user.id

In the register route:

session[:user_id] = @user.id

In the logout route:

session[:user_id] = nil

controllers/ItemController.rb

In the item create route:

@item.user_id = session[:user_id]

πŸ”΄ Commit: "Stores user_id in session. Updated item create route to use it."


25. Using bcrypt with sinatra

For this step we will need to modify our database structure to store password hashes (ActiveRecord calls them password_digests) instead of plain-text passwords.

  • Stop your server.
  • In your migrations file, in the CREATE TABLE users statement, change password to be: password_digest. It MUST be VARCHAR(60)--don't use a different number. The rest of the file is fine as is.
  • Open psql. Drop the entire database.
  • Copy db/migrations.sql and paste it into the psql console.
  • Do whatever investigating you need to do in the psql console to convince yourself everything went as you hoped.

Now, here's a little more ActiveRecord fairy dust. Here's how you set up the user model to bcrypt...just add one line:

models/UserModel.rb

class User < ActiveRecord::Base
	has_secure_password # ridiculous
	
end

Now, if you're trigger happy, and you already tried it, you'll see that we're not quite done yet. You must update your login route where you check the password:

controllers/UserController.rb, in the login route:

First, grab the password in the very first line of the route:

post '/login' do
	@pw = params[:password]

...	

Then, a few lines down, change your if statement after User.find or User.find_by:

		# this is what you probably had before adding crypt. delete it
		# if @user && @user.password == params[:password] 

		# ... and add this instead
		if @user && @user.authenticate(@pw)

If everything went as planned, then you just implemented bcrypt authentication! Create a username.... actually, create a few usernames, and check in psql (SELECT * FROM USERS;) and you should see that the passwords are being encrypted.


πŸ”΄ Commit: "Added bcrypt"


26. Implement relations to fully achieve multiple users.

Ok here's some sweet ActiveRecord magic stuff.

Set up the relations. A user "has many" items, and each item "belongs to" one user. These are "relational data" phrases. They are not arbitrary.

So.....

models/ItemModel.rb

class Item < ActiveRecord::Base
	belongs_to :user # add this
end

and...

models/UserModel.rb

class User < ActiveRecord::Base
	has_secure_password

	has_many :items # add this
end

Then in your item index route you can just do this:

controllers/ItemController.rb

		# @items = Item.all # delete this and replace with: 

		@user = User.find session[:user_id]

		# How cool is this
		@items = @user.items

Now if you log in with different users and add items, you should only see the items for whoever you're logged in as.

Once again, head on over to your console and check out what SQL this is writing for you. Pretty sweet!

Did you notice that we have not done much to prevent hijacking -- ie, someone could update an item that's not theirs. How would that work and how could we stop it? Where are holes in our "security"?


πŸ”΄ Commit: "Added relations and updated item index to use it"


Cool so, now you can pretty do everything with Sinatra that you were doing with Express.

You completed the lab/homework! Good job!

Hungry for more?

Separate the back and front end:

  • Add jQuery to the layout.erb.

  • Create additional routes (leave the old ones) that do the same CRUD operations using ActiveRecord, but that only send back JSON.

  • In the JSON responses, include a message in the JSON saying if it was successful or something like that. Make that part identical for all the routes. In general make the JSON responses as consistently formatted as possible.

  • Once the user logs in, render (or provide a link to) a new view :item_index_ajax that is similar to item_index but without the <forms>. Do NOT print the items with erb. Instead, add client-side JS AT THE BOTTOM of item_index_ajax, and in your client-side JS, have an ajax 'GET' call that runs when the page loads. If it gets a successful response, that AJAX call's will include (or call a function that includes) logic to add the items to the page using jQuery DOM manipulation.

  • Each item will still have a delete <button>. Just no forms. There won't be any <form>s at all.

  • For updating, there will be an "Edit" link for each item that will show a input (NO <FORM>, just <input>) with the item data already populated, and an "update button."

  • There will also be an 'Add Item' input and an 'Add Item' button that are always on the page.

  • Make the add, delete, and update buttons send AJAX requests (using jQuery's $.ajax) to your new routes, and when they get JSON back, they will update the html on the page accordingly with DOM manipulation. There should be no page refreshing for any /item routes.

  • Rember to clear the Add Item input field when you do a successful add, and hide the update form once you've successfully updated.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published