Doing This Odin Project task
Contents
- Ruby on Rails Project
- Actual README
- My Own Notes
- Expected Basics Covered
- Basic Read Features
- Steps:
- 1. Creating new rails application
- 2. Configuring the Database in database.yml
- 3. Starting the Server, to check things are in order
- 4. Creating the Article Model
- 5. Working w the database
- 6. working with a model in the console
- 7. Setting up the Router
- 8. Creating the Object's Controller
- 9. Defining the Index Action
- 10. Creating the template
- 11. Adding Navigation to the Index
- 12. Creating SHOW Action
- 13. Styling your Templates
- Steps:
- Additional Create Features
- Commenting Features
- Steps:
- 1. Creating the Comment Model
- 2. Setting up the Migration
- 3. Understanding Relationships b/w elements of data
- 4.Displaying Comments for an Article
- 5. Creating a Web Interface for creating comments: Web-Based Comment Creation
- Comment Form Partial: embeding a form onto a view template and creating the partial form template
- updating the ArticlesController for the comment
- Improving the Partial Comment form template
- Updating the Router to create the necessary paths
- Creating a CommentsController
- Writing the create method within CommentsController
- Adding Comments Count
- Adding Form Labels
- Adding Timestamp to comment display
- Steps:
- Tagging Features
- Steps:
- 1. Understanding the Relationship: database modelling
- 2. Making Models: Tag and Tagging using Rails model generator
- 3. Expressing relationships b/w models by adding relationships to respective models/<model>.rb files
- 4. An Interface for Tagging Articles
- 5. Adding Tags to Our Display
- 6. Listing Articles by Tag
- Steps:
- Using Gems
- The paperclip library
- 1. Using the Gemfile to Setup a RubyGEm
- 2. Setting up The Database for Paperclip
- 3. Add to the Article Model
- 4. Modifying the Form Template
- 5. Improving the Form (seems like latest gem version aldy does this automatically)
- 6. Allowing Multiple Image Attachements [HELP I CAN'T DO THIS :(]
- 7. SASS examples
- 8. Working with Layouts: how to manually set layouts
- The paperclip library
- Implementing Authentication
- Heroku Deployment
- More Important Takeaways & Reminders
This README would normally document whatever steps are necessary to get the application up and running.
Things you may want to cover:
-
Ruby version
-
System dependencies
-
Configuration
-
Database creation
-
Database initialization
-
How to run the test suite
-
Services (job queues, cache servers, search engines, etc.)
-
Deployment instructions
-
...
- MVC
- Data Structures and Relationships
- Routing
- Migrations
- Views with forms, partials and helpers
- RESTful design
- Adding gems for extra features
- this generates:
- a database migration to create the
articles
table - file that holds the model code
- a fixtures file
.yml
to assist us in unit testing
- a database migration to create the
- migration files used to modify database, generatlly database agnostic
- write your migrations once, and run them amongst almost any database
- change the
change
method in the migration file- the variable
t
is referring to the article table created automatically in the db
- the variable
- running the migration via
bin/rake db:migrate
- every table created with a migration will automatically have an
id
column that serves as the primary key
- cli console to interact w the webapp
- can do bulk modifications, searches and other data operations
- rmb to enter the development environment by running
bin/rails console
in the project's working directory
-
As part of MVC model, we touched on Model, now we need Controller and View
-
HTTP requests handled by Router. Decides what resources the browser is trying to interact w
- Dissects the address that the HTTP request is being made to
- other HTTP parameters like GET and PUT
-
router config file:
config/routes.rb
-
add resource attributes here that allows the router to follow the RESTful model of web interaction
-
Now, router knows how to handle requests about the model object, it needs a place to actually send those requests to: the Controller
-
-
use another rails generator :
bin/rails generate controller articles
-
run
$ bin/rake routes
- The
object_controller.rb
has methods for actions to be done.- Use Instance Variable syntax for the Methods to create Instance level variables that can be accessed by both the controller and the view so that other variables outside the
index
method and within the same object can access it
- Use Instance Variable syntax for the Methods to create Instance level variables that can be accessed by both the controller and the view so that other variables outside the
-
app will search for a view template in
app/views/articles/
in which it shall search for a.erb
template file -
template naming:
.html.erb
: the HTML clarifies that this view is for generating HTML, if creating things like RSS feeds, then we'd create something like.xml.erb
-
erb clauses:
<%
: the result of the code will be hidden<=%
: the result of the ruby code will be output in place of the clause- note that you can add in html element tags into the erb tags
(?) how come we don't need to put doctype tag at the top of the .html.erb file?
- Ref to Routing Table to see route names that will be used for links
- "route helper" is used to specify where the link will point to. route helper looks like this:
article_path(id)
- in the template file, we add the links, using a link helper see index template file
- can add new article creation links at the bottom, similarly. See where you have to point to based on the routing table, then create link using link helper.
- now, there's a link but there's no
show
action (creating a show method of the controller)
- what do we want to do when the user clicks an article title? Find the article, then display a page with its title and body. We’ll use the number on the end of the URL to find the article in the database.
- place css sheets, appropriately named at
app/assets/stylesheets/
Creating an HTML form to submit the article, then backend processing to get it into the database
- a template for new articles is needed
- use Rails helper method
form_for
to make the form - Setting up for Reflection:
- you have to create the object that the instance variable will pass to
form_for
method. To do this, instantiate in thenew
method itself, where you assign the@article
instance variable to a new Article objectArticle.new
to prevent the variable from holdingnil
as a variable and therefore throwing error.
- you have to create the object that the instance variable will pass to
-
have to define a create method within the
ArticlesController
class, then direct the view to some template.- we don't have to create a new view template because we already have
show
and the template for that. Here, we just need to use the show template
- we don't have to create a new view template because we already have
-
Before giving a view, we Process the Data.
- Momentarily use
fail
method within thecreate
method to halt the request so you can examine request parameters in the runtime error. Then, you'd know how to analyse the form parameters and access the parameters you require. - Pull Out Form Data data via the
params
method and assign to instance variables - rmb to put in a redirect action. e.g.:
redirect_to article_path(@article)
- we can't blindly save params sent into us via the params hash because of security reasons, so you have to use Strong Parameters:
- use
require
andpermit
to declare which attributes to accept (create a helper method for this in the dir for helpers:app/helpers/articles_helper.rb
) - import the helper module into the
ArticlesController
class, after which you can use thearticle_params
which you have written in the helper, that uses strong params
- use
- Momentarily use
-
add delete links to the index. In the tutorial, we add it to the show template:
-
using
link_to
helper, create a link. The path for link is taken from routing table, where we look for thedestroy
action- link will look something like this:
<%= link_to "delete", article_path(@article) %>
- link will look something like this:
-
have to take note of the VERB used. HTML forms only support
GET
andPOST
. So to delete, you need to add this to the template:<%= link_to "delete", article_path(@article), method: :delete %>
- this is some Rails hack where the
DELETE
verb is faked by using some JavaScript tricks and triggerring the deletion.
-
HTTP verb
DELETE
uses the Rails methodDESTROY
and for this, we need adestroy
method within ourArticlesController
class- rmb after the action, you have to describe where to redirect to!
-
rmb to ask for a deletion confirmation by adding a method call to
confirm
:data: {confirm: "Really delete the article?"}
- confirm will make a dialogue box pop up
-
add the edit link in
show.html.erb
first- Trigger the
edit_article
route and pass in the@article
object
- Trigger the
-
define the
edit
action in the controller -
create template form for editing, use partial templates for this.
-
implement the
update
method in controller.- you can expect it too look very similar to create
- the
update
method takes in the hash of form data, changes the values in the object to match the values submitted with the form and automatically saves the changes (that's why you don't need to use thesave
method)
-
Adding a flash message is just a one-line insertion into the controller method, followed by invoking a view.
- could have added to our show template itself, but here, we want to use view many times, so just create a layout
-
add a
flash
for the update action, by adding a notice to theapp/views/layouts/application.html.erb
file
- in
config/routes.rb
, make the root show the articles index page by adding:root to: 'articles#index'
-
brainstorming what it will be:
- what kind of data does it have?
- where will it be attached to?
- what other attributes will it have?
- author name?
- body?
-
use generator to create the comment model, passing in the field you want in the db
- important files to take note of:
comment.rb
- migration file
- important files to take note of:
- check the newly created migration file for the model that has been created, in this case the migration file would look something like this:
db/migrate/some-timestamp_create_comments.rb
- the fields added would be:
:author_name
:body
:article
- the fields added would be:
- run the migration in the project's root dir:
bin/rake db:migrate
-
Or in our case here, join together an article in the articles table with its comments in the comments table. We do this by using foreign keys.
-
Foreign keys are a way of marking one-to-one and one-to-many relationships.
- e.g. 0, 5 or 100 comments may exist for one article, but one article connects to that many comments
-
Rails makes it simple to work with relationships. When we created the migration for comments we started with an
references
field namedarticle
. So this exemplies the convention for one-to-many relationships:- the objects on the "many" end should have a foreign key referencing the "one" object.
- that foreign key should be titled with the name of the "one" object, then an underscore, then "id".
here, one article has many comments, so each comment has a field named
article_id
-
this relationship is reflected in the respective model files
comment.rb
andarticle.rb
files.- Ensure you have declared the one-side of the relationship! not everything will be autoadded
- test out comment creation and adding comment attribute values
create
vsnew
methods: if you usenew
then you have to explicitlysave
the change.create
automatically does both- rmb to reload the irb so that it doesn't lazily fetch things from the cache
- figure out where you want to display it, go to that template and
render
a partial for a collection of comments (iterating thru a array that is comments) - create the partial template
-
we want to enter the comment directly onto the article page, instead of being redirected to another form view, so we embed the new comment form onto the article show
-
insert the
render
into the correct view template.- this template will look for
comment/form
so, - create the subdir
comments
insideapp/views
- this template will look for
- we want to create a blank comment object within the
show
method for the reflection to happen (?) - use
Comment.new
instead of@article.comments.new
. If not, you'll have an extra comment created. That is due to the fact that@article.comments.new
has added the newComment
to the in-memory collection for theArticle
. Don’t forget to change this back. - The
Comment
object inside thatshow
method has to be manually assigned with theid
of theArticle
, thanks to Rails’ mass-assignment protection.
- the
form_for
helper is trying to build the form and then submit it toarticles_comments_path
but for this path to exist, you have to provide the router this path, so config it atconfig/routes.rb
-
use generator to generate controller:
bin/rails generate controller comments
-
instructions are similar to the
create
method inarticles_controller.rb
- replace
Article
object withComment
object
- replace
-
NB: take note of how to assign the article id to our comment:
def create @comment = Comment.new(comment_params) @comment.article_id = params[:article_id] @comment.save redirect_to article_path(@comment.article) end def comment_params params.require(:comment).permit(:author_name, :body) end
- just modify the comments header
- change the forms' labels so it makes more contextual sense.
- update the partial template at
comments/_form.html.erb
- useful rails helper called
distance_of_time_in_words
- add to
_comment.html.erb
the comment view partial
- tagging for organisation and navigation
- think: how to relate to the Article model?
- A tag must have a relationship to an article so they can be connected
- consider both models: tags and articles. they have a many-to-many relationship
- In SQL database, we would put the foreign key (
article_id
) inside thetags
table- then, a Tag would "belong" to an Article.
- but then a Tag can connect to many articles, not just one.
- SO, just
articles
andtags
tables are not enough to model this relationship
-
having a "join table" called
articles_tags
-
Article model would have a
has_and_belongs_to_many
r/s w Tags -
Tag model would have a
has_and_belongs_to_many
r/s w Articles
-
-
The connection b/w the two models has a value of its own, so make it a real model:
- model called
Tagging
(the connection b/w Tags and Articles) - an Article
has_many
Taggings - a Tag
has_many
Taggings - a Tagging
belongs_to
and Article andbelongs_to
a Tag
- field for Tag:
name
- fields for Tagging: the types of the field are
references
tag_id
: Integer holding the foreign key of the referenced Tagarticle_id
: Integer holding the foreign key of the referenced Article
- rmb to migrate dbs after creating models
3. Expressing relationships b/w models by adding relationships to respective models/<model>.rb
files
- now an Article has a list of tags through the relationship of taggings.
- in Rails, we express this "has many" relationship though an existing "has many" relationship. Update models to expresss that relationship through another "has_many" relationship:
has_many :tags, through: :taggings
has_many :articles, through: :taggings
- in Rails, we express this "has many" relationship though an existing "has many" relationship. Update models to expresss that relationship through another "has_many" relationship:
- Now if we have an object like article we can just ask for
article.tags
or, conversely, if we have an object named tag we can ask fortag.articles
.
-
update the partial form template such that I can now input tags as comma separated words.
-
then need to add the
tag_list
method to the Article model. Note that the method is written in the model's ruby file -
now, the array of tags is an array of Tag instances, so joining the array, Ruby called the
#to_s
method on each of the Tag instances. This gives some hexadecimal output and is unwanted. Make sure thetag_list
method does:-
converts tag objects to an array of tag names
-
joins the array of tag names together
def tag_list self.tags.collect do |tag| tag.name end.join(", ") end
-
-
OR: define a new
Tag#to_s
method:class Tag < ActiveRecord::Base has_many :taggings has_many :articles, through: :taggings def to_s name end end
-
make sure you handle ruby's strong parameters properly, else you'll get unpermitted parameters error
- update
app/helpers/articles_helper.rb
to fixarticle_params
method - now you'll have to update the
create
method in theArticlesController
to account for the new attribute oftag_list
- update
-
Since the
create
method passes all the parameters from the form into theArticle.new
method, the tags are sent in as the string "technology, ruby". Thenew
method will try to set the new Article’stag_list
equal to"technology, ruby"
but that method (thatnew
method(?)) doesn’t exist because there is no attribute namedtag_list
.- Solutions:
- [easiest solution:] pretend that there's an attribute called
tag_list
,- define
tag_list=
method insidearticle.rb
, without deleting the originaltag_list
method. This is probably a setter method (?)
- define
- [easiest solution:] pretend that there's an attribute called
- Solutions:
-
now, we didn't create any tags either. We need to associate the article with tags that have the names... pseudocode:
1: Split the tags_string into an array of strings with leading and trailing whitespace removed (so
"tag1, tag2, tag3"
would become["tag1","tag2","tag3"]
So first we split the string, and then trim each and every element and collect those updated items.Thedowncase
method is to make sure that "ruby" and "Ruby" don’t end up as different tags. Lastly, we want to make sure that each and every tag in the list is unique.Array#uniq
allows us to remove duplicate items from an array. ruby code to be added totag_list=
method:"programming, Ruby, rails, rails".split(",").collect{|s| s.strip.downcase}.uniq
2: For each of those strings…
2a: Ensure each one of these strings are unique
2b: Look for a Tag object with that name. If there isn’t one, create it.
tag = Tag.find_or_create_by(name: tag_name)
2c: Add the tag object to a list of tags for the article
3: Set the article’s tags to the list of tags that we have found and/or created. eventual
tag_list=
method:def tag_list=(tags_string) tag_names = tags_string.split(",").collect{|s| s.strip.downcase}.uniq new_or_found_tags = tag_names.collect { |name| Tag.find_or_create_by(name: name) } self.tags = new_or_found_tags end
- modify show template to include tags and links to tags
- we need to let the router know about our Tag object, so you'll have to generate a controller for our Tag object
bin/rails generate controller tags
- update
config/routes.rb
- the links to the tags should list articles by Tag, so we need to add an action for that, and a corresponding template (justupdate the show template)
Library that manages file attachments and uploading
- old way: using zip or tar files. but this isn't easy to just upgrade to newer versions
- now we have Rails plugins as gems.
- RubyGems is akin to apt or RPM... a package manager
- Add
gem "paperclip"
to the Gemfile of the project - Install
ImageMagick
via apt (ensure it's there) - Stop running server, run
bundle
to install the required Gem(s) and the restart the server
-
for now, just say that the article can have zero or one image, just for demo sake
-
Add some fields to Article model to hold the uploaded image, rmb to run a migration
- it's a one-liner:
bin/rails generate migration add_paperclip_fields_to_article
- it's a one-liner:
-
udpate the
change
method inside the_add_paperclip_fields_to_article.rb
file and then run a migration by doingrake db:migrate
-
has_attached_file
is a method from the paperclip lib. Allows paperclip to understand that the Article model should accept a file attachment and that there are fields to store info about that file which start withimage_
-
required to include a content_type validation, a file_name validation, or to explicitly state that they’re not going to have either.
- if not, you get
MissingRequiredValidatorError
error
- if not, you get
-
also, deal w mass assignment and strong parameters by updating the
article_params
method to permit images
- so we can upload file while editing the article
- add the image display to the article show template, update the form partial template:
- Allow the form to accept multipart data.
- Add a label and a field for file uploading before the save button
- update the article show view to display the image before the body of the article is placed
- in the form, allow the file_field to indicate whether a file has been updated and if so, which file has been updated
- a model(here, it's an Article) can have multiple attachements.
- Create a new model, call it "Attachment"
- can just add style sheets and name it
style.css.scss
and Rails will handle the referencing to that sheet thanks to Rails' default layout
-
in each template, add a reference style like so:
<%= stylesheet_link_tag 'styles' %>
- this finds the stylesheet with the tag 'styles'
- but this is unnecessarily repetitive so we look to using layouts
-
see
app/views/layouts/application.html.erb
- Whatever code is in the individual view template gets inserted into the layout where you see the
yield
. Using layouts makes it easy to add site-wide elements like navigation, sidebars, and so forth. - this layout points to
app/assets/stylesheets/application.css
asindicated by thestylesheet_link_tag
line
- Whatever code is in the individual view template gets inserted into the layout where you see the
common but complicated authentication gems:
- rmb to restart Rails server each time you update the Gemfile
- use
rails generate
to check which and if all gems installed properly
- the plugin gives a generator that creates a model that represents our user and the required data migrations to support authentication
- just run the default generator:
bin/rails generate sorcery:install --model=Author
- revise the SourceryCore migration that the generator has created, add/remove fields that you need in the database here
- then do the database migration migration:
bin/rake db:migrate
-
we could choose to use the console and create users and test the workflow, but noooo
-
create and test our form-based workflow by creating a user through it
-
we need to add CRUD support for our Author model! To this, instead of doing it manually, allow rails to generate the scaffolds :
bin/rails generate scaffold_controller Author username:string email:string password:password password_confirmation:password
-
used scaffold_controller because Sorcery aldy defined for us an author model
-
rails has two scaffolds generators:
scaffold
: generates the model, controller and viewsscaffold_controller
:generate the controller and views
-
scaffold generator didn't know that we want our password field and password confirmation field to use a password text entry, so we have to update
authors/_form.html.erb
and change the field type -
allow for checking for password and password confirmation fields in that form. Add the
validates_confirmation_of
method call to the Author model itself -
note that
password
andpassword_confirmation
fields are virtual attributes. Sorcery uses given password along with the auto generatedsalt
value to create and store thecrypted_password
-
need to update
routes.rb
to add a resource for our Authors -
remove hash and salt from author view > show and index templates
-
add footer to
app/views/layouts/application.html.erb
so that there's a login status at the footer
-
-
need to build the actual endpoints for logging in and out, meaning controller actions:
- AuthorSessions controller with these actions:
- new
- create
- destroy
- generate controller w rails generator and add the respective methods
- AuthorSessions controller with these actions:
-
create new view template for the
new
action that will contain the login form -
config the routes to include the author_sessions ressources
-
update the
app/views/layouts/application.html.erb
to include login page links in the footer layout
-
Let’s add in a protection scheme like this to the new users form:
- If there are zero users in the system, let anyone access the form
- If there are more than zero users registered, only users already logged in can access this form
That way when the app is first setup we can create an account, then new users can only be created by a logged in user.
-
before_filter
method that runs beforethenew
andcreate
actions ofauthors_controller.rb
heroku create
-
delete
gem 'sqlite3'
group :development, :test do gem 'sqlite3' end group :production do gem 'pg' end
-
ofc after Gemfile change, update bundle
- edit
routes.rb
to set our root route if haven't.root 'cars#index'
<--- example of what the root route should point at
-
rails
vsbin/rails
. the latter is used to run scripts like thegenerate
script -
migration files:
db/migrate/(some_time_stamp)_create_articles.rb
, the timestamp part is useful for organisation- the migration file only has one method called
change
where we write out how to do theup
migration (the change), the undo methods are handled by Rails! - dbs have type
text
orvarchar
for large text fields. database adapter takes care of figuring out the best type, depending on the db in the config
-
rake
is for running maintenance-like functions on your application (working with the DB, executing unit tests, deploying to a server, etc). -
There's a technique called reflection whereby Rails queries the db, looks at the articles table, and assumes whatever columns that table has that should be the attributes for the model.
-
RESTful model of Web interaction: (REpresentational State Transfer)
- 7 Core actions of Rails' REST implementation
-
The
params
method within the controller is a godsend. Returns the hash of the request parameters. See the various keys -
In the routing table, names "trickle down" so if one of the rows have no routes, then just loop up to the row above it...
-
The controller, being a middleman in the MVC framework, should know as little as possible to get its job done.
- consider creating an instance and passing it a hash of attributes instead of one-by-one declaring instance attributes in the
create method
- you can't blindly save params sent into us via the params hash because of security reasons, so you have to use Strong Parameters:
- use
require
andpermit
to declare which attributes to accept (create a helper method for this in the dir for helpers:app/helpers/articles_helper.rb
) - import the helper module into the
ArticlesController
class, after which you can use thearticle_params
which you have written in the helper, that uses strong params
- use
- consider creating an instance and passing it a hash of attributes instead of one-by-one declaring instance attributes in the
-
Abstracting template files as partials!
- partial file names always start with underscore
_form.html.erb
- render partials in other template files like so:
<%= render partial: 'form' %>
-
Layouts wrap multiple view templates in our application. Layouts can be specific to each controller , but usually we just use one layout that wraps every view template in the application
-
Referential Integrity has to be enforced for you to be able to delete tags, kiv this concept for the future
-
Asset Pipeline Principle: a
require_tree
method auto loads all the stylesheets in the current directory, and includes them inapplication.css
-
Virtual Attributes in forms
- manage security:
-
before filters runs before new and create but I don't know where to put it in its controller
before_filter :zero_authors_or_authenticated, only: [:new, :create] def zero_authors_or_authenticated unless Author.count == 0 || current_user redirect_to root_path return false end end
-
also, control which links can be seen when like so:
<% if logged_in? %> <%= link_to "delete", article_path(@article), method: :delete , data: {confirm: "you sure you want to delete the post bruh??"}%> <%= link_to "edit", edit_article_path(@article) %> <% end %>