- Image upload: paperclip (gem)
- Image upload requires ImageMagick
- Google Maps API: with the geocoder (gem)
- Figaro: for hiding API key (and other sensitive data)
- User
- password
- Post
- title
- cost
- body
- image
- address
- latitude
- longitude
- user_id
rails new craigslist-clone
cd craigslist-clone
git init
(add and commit)- Setup User authentication with Devise
- Add paperclip to Gemfile and
bundle install
- Generate Post model
rails g model Post title cost:decimal body:text
rails g paperclip post image
rake db:migrate
- Add paperclip code for image to Post model
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/
- Generate posts_controller
rails g controller Posts index new show edit
- Add post routes to
config/routes.rb
using the resources heler and remove default generated routesresources :posts
- Add simple_form to Gemfile
rails g simple_form:install --bootstrap
- Add new form for Post
<%= simple_form_for @post do |f| %>
<%= f.input :title %>
<%= f.input :cost %>
<%= f.input :body %>
<%= f.input :image, as: :file %>
<%= f.button :submit %>
<% end %>
-
Add
post_params
andcreate
action to posts_controller -
Install ImageMagick
- mac -
brew install imagemagick
- c9
sudo apt-get update
sudo apt-get install imagemagick -y
- mac -
-
Fire up your app and navigate to
localhost:3000/posts/new
, create a new post -
Edit your
posts_controller
show
action andposts/show.html.erb
view to display the Post info- use
number_to_currency
andimage_tag
to render the cost and the image
- use
- Complete full CRUD for Posts
- Associate User and Post (one:many)
- Add post validations and error rendering
- Add authorization to stop malicious editing or deleting of posts
See c9 workspace for source code (navigate to: tts-dal/rails/day_17_craigslist_clone_part_1/craigslist-clone
)
- Add figaro gem to Gemfile
gem 'figaro'
- Run
bundle
- Run
bundle exec figaro install
- Create a new project in Google Maps API and get a key
- Open
config/application.yml
and add your key as an environment (ENV) variablemaps_api_key: <your_key_here>
- open
application.html.erb
and add the following script to the bottom of the head element:
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?key=<%= ENV['maps_api_key'] %>">
</script>
- Add a div to render the map onto your
posts/show.html.erb
view:
<div id="map-container">
<div id="map-canvas"></div>
</div>
- Open
assets/stylesheets/posts.scss
, rename the file to beposts.css
and add the following CSS:
#map-container {
height: 400px;
border-radius: 16px 16px;
border-color: #fff;
border-style: solid;
box-shadow: 2px 2px 10px #B1B1B1;
margin-top: 25px;
border-width: 7px;
}
#map-canvas {
height: 384px;
width: 100%;
}
- Open
assets/javascripts/posts.coffee
, rename the file to beposts.js
and add the following code:
$(document).ready(function (){
function initialize() {
var myLatlng = new google.maps.LatLng(33.784624, -84.422030);
var mapOptions = {
zoom: 15,
center: myLatlng,
scrollwheel: false
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
animation: google.maps.Animation.DROP
});
}
google.maps.event.addDomListener(window, 'load', initialize);
});
- Now we need to give our Post model an address property and connect it to the map
- Generate a new migration for the posts table:
rails g migration AddCoordinatesToPosts latitude:float longitude:float address:string
then runrake db:migrate
- Now add the address property to our
posts/new.html.erb
form:
<%= simple_form_for @post do |f| %>
<%= f.input :title %>
<%= f.input :cost %>
<%= f.input :body %>
<%= f.input :image, as: :file %>
<%= f.input :address %>
<%= f.button :submit %>
<% end %>
- Whitelist the new properties in your posts controller:
def post_params
params.require(:post).permit(:title, :cost, :body, :image, :latitude, :longitude, :address)
end
- Now add the geocoder gem:
- Add
gem geocoder
toGemfile
and runbundle
- Add
- Add the following code to your Post model (
models/post.rb
), right after the paperclip code that we added earlier:
geocoded_by :address
after_validation :geocode
- To render the address and marker onto the map we'll need to set the JavaScript variables using Rail's javascript_tag, add the following code to the bottom of
posts/show.erb
<%= javascript_tag do %>
latitude = '<%= j @post.latitude.to_s %>';
longitude = '<%= j @post.longitude.to_s %>';
title = '<%= j @post.title %>';
cost = '<%= j number_to_currency(@post.cost) %>';
address = '<%= j @post.address %>';
<% end %>
- I went ahead and included the title, cost, and address so we could render them onto the marker as an info window
- Update your
assets/javascripts/posts.js
file with the following:
- Update your
$(document).ready(function (){
function initialize() {
var myLatlng = new google.maps.LatLng(latitude, longitude);
var mapOptions = {
zoom: 15,
center: myLatlng,
scrollwheel: false
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
animation: google.maps.Animation.DROP
});
var contentString = '<h1>' + title + '</h1>'+
'<p>' + cost + '</p>'+
'<p>' + address + '</p>';
var infowindow = new google.maps.InfoWindow({
content: contentString
});
marker.addListener('click', function() {
infowindow.open(map, marker);
});
}
google.maps.event.addDomListener(window, 'load', initialize);
});
- Open up
rails console
and delete all pre-exisiting posts withPost.destroy_all
now close rails console, run yourrails server
and create a new post.
Do you have to refresh the page several times for the map to load? This is because of turbolinks. Turbolinks is rails gem that conflicts with our javascript. Funny, because its intended purpose is to load pages faster. Let's kill it.
- Remove the gem 'turbolinks' line from your Gemfile.
- Remove the //= require turbolinks from your app/assets/javascripts/application.js.
- Remove the two "data-turbolinks-track" => true hash key/value pairs from yourapp/views/layouts/application.html.erb.
- Open up your
application.html.erb
file and modify the navbar. In the following code we are removing the html form and adding in a rails form with ERB syntax:
<nav class="navbar navbar-default">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<%= link_to 'Craigslist Clone', root_path, class: 'navbar-brand' %>
</div>
<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="<%= 'active' if current_page?(posts_path) %>"><%= link_to 'Posts', posts_path %></li>
<li class="<%= 'active' if current_page?(new_post_path) %>"><%= link_to 'New Post', new_post_path %></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<!-- Search form -->
<%= form_tag posts_path, :method => 'get', class: 'navbar-form navbar-left' do %>
<div class='form-group'>
<%= text_field_tag :search, params[:search], class: 'form-control', placeholder: 'Search' %>
</div>
<%= submit_tag "Search", :name => nil, class: 'btn btn-default' %>
<% end %>
<% if user_signed_in? %>
<li><%= link_to current_user.email, edit_user_registration_path, :class => 'navbar-link' %></li>
<li><%= link_to "Logout", destroy_user_session_path, method: :delete, :class => 'navbar-link' %></li>
<% else %>
<li><%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link' %></li>
<li><%= link_to "Login", new_user_session_path, :class => 'navbar-link' %></li>
<% end %>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
- Open up the
posts_controller.rb
file and replace the index action with the following code:
def index
@posts = Post.search(params[:search])
end
- .search is not a regular class method for the Post model, so we'll have to create it ourselves, open up your post model
models/post.rb
and add the following code to the bottom of your model before the closingend
keyword:
def self.search(search)
if search
search = search.downcase
where('lower(title) LIKE ?', "%#{search}%")
else
all
end
end
-
Fire up your server and check it out, you should now have the ability to search for any existing posts using keywords from their titles
-
Discuss SQL Injection Security Exploit
- Install the twitter-bootstrap-rails gem
- download a free theme from startbootstrap.com (we used clean blog)
- unzip the theme
- copy and paste images dir into public
- copy and paste the minified css theme into vendor/assets/stylesheets - require in application.css (e.g. require clean-blog.min)
- copy and paste the minified js file into vendor/assets/javascripts - require in application.js
- open index.html, find any google fonts, copy and paste their - import codes from fonts.google.com into application.css using @import url()
- open index.html and copy over the navbar and header areas, be sure to replace the appropriate elements with your pre-existing rails content