- install postgres
brew install postgresql
ln -sfv /usr/local/opt/postgresql/*.plist ~/Library/LaunchAgents
~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist
- install rails
gem install rails
- T enables testing
rails new awesome_answers -d postgresql -T
# or if want to use react
rails new jscout-app --webpack=react
-
database connection info is stored in config/database.yml
-
start rails server
bin/rake db:create
bin/rails server
bin/rails s -p 3001
- adding new gem requires restarting the server and run:
bundle install
##MVC
-
Model view controller
-
model interacts with the database
-
response to client is sent in html, css, javascript format
-
routerdecides which controller to use -
routes.rb defines all url
# This will map any GET request with path '/hello' to welcomecontroller
# and index action within that controller
get "/hello" => "welcome#index"
-puts the higher priority routes at the top because the order of routes matters
- in console: generate a controller
bin/rails g controller welcome
-
They generate a welcome_controller.rb in controller folder
-
Rails use a lot of meta programming
-
All controllers inherit from application_controller
-
in welcome_controller.index, define actions
class WelcomeController < ApplicationController
# we call instance methods defined in a controller 'actions'.
# we need actions in order to handle user requests
# below will let rail to automatically look for an index.html template
def index
end
end
- in vews/welcome, create a welcome.index.erb file
####index
- controller action
####html
- format
####erb
- templating system
####checks for routing
http://localhost:3000/rails/info/routes
or
bin/rake routes
- in route
get "/subscribe" => "subscribe#index"
- in application.html.erb
<%= link_to "Subscribe", subscribe_path %>
- in command line
bin/rails g controller subscribe
- go to the subscribe controller.rb
def index
# This renders the app/views/welcome/about.html.erb template with no layout
# render "/welcome/about"
end
- go to views/subscribe, create a new file "index.html.erb"
<h1>subscribe</h1>
####form
post "/subscribe" => "subscribe#index"
- index.html.erb under view/subscribe
<h1>Subscribe to our newsletter</h1>
<%# form_tag is a built-in rails view helper that enables us to
generate a form the first argument is the 'action' attirbute of the form.
you can provide method: attribute to make the form do non-POST request %>
<%= form_tag subscribe_path, method: :post do %>
<%end %>
- in views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>AwesomeAnswers</title>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
<%= csrf_meta_tags %>
</head>
<body>
<%= yield %>
<%# Hello_path and about_path are called route helpers.
they get generated from the content of 'routes.rb'
and they are methods that can be used in all view files and all controllers %>
<a href="<%= hello_path %>">Hello</a>
<a href="<%= about_path %>">About</a>
<%# <a href="<%= gree_path(name: 'John') ">Greet John</a>%>
<a href="<%= greet_path('John') %>">Greet John</a>
<%# link_to is a helper method that is built-in with rails, it's a view helper method,
meaning it's accessible only in the view/view helpers. link_to
take a first argument which is the anchor text and a second argument which is
the 'href' value (url/path). It can take other options as we will see later
http://api.rubyonrails.org/classes/ActionView/Helpers/UrlHelper.html#method-i-link_to %>
<%= link_to "Hello", hello_path %>
<%= link_to "Subscribe", subscribe_path %>
</body>
</html>
- below is automatically generated, can be observed using inspect element
- authenticity token
- curl -X POST http://loalhost:3000/subscribe
<input type="hidden" name="authenticity_token" value="szPlMvnERrIZcBChbIZVs3P5Xb3UDof+7fJmA/ZlmGCjlhF+rIQKe0wQv+5WVeNmFYatVhBzh3sCPWPv65lUBQ==">
- go to the subscribe controller.rb
def create
render text: params
end
####namespace
- create our controller path
- same view path
- do need to define routes any more
namespace :admin do
resources :questions, :answers
end
scope: "/admin" do
resources :question
end
- model (model name should be singular) maps to one table one row in a table (table name should be plural)
- eg. Question maps to questions
bin/rails g model question title:string body:text
- title attribute in string
- body attribute in text
- inherits ActiveRecord::Base
- model maps something in the database
- inherits ActiveRecord::Migration
- instruction we give to the database to create tables with the correct structure
- timstamps will add two date time fields: created_at and updated_at
- execute migration
bin/rake db:migrate
- no need to specify ID column, ActiveRecord automatically creates an integer field called 'id' with AUTOINCREMENT
####pgAdmin
- username for localhost is the username of the laptop (whoami)
- if made mistake in schema, you can:
roll backif there is no data in the database yet
bin/rake db:rollback
bin/rake db:migrate
- if there is data, create a new migration
bin/rails g migration add_view_count_to_questions
- locate the migration file and add in your query
class AddViewCountToQuestions < ActiveRecord::Migration
def change
add_column :questions, :view_count, :integer
end
end
- execute migrate again
bin/rake db:migrate
- adding rows in a table
bin/rails c
2.2.3 :001 > q = Question.new
+----+-------+------+------------+------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+-------+------+------------+------------+------------+
| | | | | | |
+----+-------+------+------------+------------+------------+
1 row in set
2.2.3 :002 > q.class
class Question < ActiveRecord::Base {
:id => :integer,
:title => :string,
:body => :text,
:created_at => :datetime,
:updated_at => :datetime,
:view_count => :integer
}
2.2.3 :003 > q.title= "My first question"
"My first question"
2.2.3 :004 > q
+----+-------------------+------+------------+------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+-------------------+------+------------+------------+------------+
| | My first question | | | | |
+----+-------------------+------+------------+------------+------------+
1 row in set
2.2.3 :016 > q.save
(0.2ms) BEGIN
SQL (1.2ms) INSERT INTO "questions" ("title", "body", "view_count", "created_at", "updated_at") VALUES ($1, $2, $3, $4, $5) RETURNING "id" [["title", "My first question"], ["body", "my first question body"], ["view_count", 0], ["created_at", "2016-02-02 18:35:09.112873"], ["updated_at", "2016-02-02 18:35:09.112873"]]
(1.3ms) COMMIT
or
irb > q1=Question.new({title: "hello world!", body:"what's up!", view_count:1})
irb > q1.save
-
by default, rails wrap every query in database transaction (BEGIN, SQL, COMMIT)
-
view all questions (select * from)
Question.all
- look for a row with a specified ID
Question.find 3
-
the find method will give u an ActiveRecord record not found exception if the condition is not valid
-
check the two ends of the table,
Question.first
Question.last
- search by a specified column value. return nil if not found
q = Question.find_by_title "my second question"
Question Load (0.4ms) SELECT "questions".* FROM "questions" WHERE "questions"."title" = $1 LIMIT 1 [["title", "my second question"]]
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| 3 | my second question | my second question body | 2016-02-02 18:46:48 UTC | 2016-02-02 18:46:48 UTC | 10 |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
1 row in set
q = Question.find_by_title "my first question"
Question Load (0.4ms) SELECT "questions".* FROM "questions" WHERE "questions"."title" = $1 LIMIT 1 [["title", "my first question"]]
nil
-
find_by_column* does not handle duplicated values; it will only return one record
-
if don't want to return all columns, can limit the returned values using select
Question.select(:id, :title).find_by_body "My first question body"
- where
> Question.where({view_count: 0})
Question Load (0.3ms) SELECT "questions".* FROM "questions" WHERE "questions"."view_count" = $1 [["view_count", 0]]
> Question.where("view_count > 0")
Question Load (0.4ms) SELECT "questions".* FROM "questions" WHERE (view_count > 0)
> Question.where({view_count: 0}).class
Question::ActiveRecord_Relation < ActiveRecord::Relation
- each
Question.where("view_count > 0").each {|x| puts x.title}
Question Load (0.4ms) SELECT "questions".* FROM "questions" WHERE (view_count > 0)
hello world!
my second question
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| 2 | hello world! | what's up! | 2016-02-02 18:43:37 UTC | 2016-02-02 18:43:37 UTC | 1 |
| 3 | my second question | my second question body | 2016-02-02 18:46:48 UTC | 2016-02-02 18:46:48 UTC | 10 |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
2 rows in set
- all
Question.where({view_count: 0, title:"abc"})
Question Load (0.2ms) SELECT "questions".* FROM "questions" WHERE "questions"."view_count" = $1 AND "questions"."title" = $2 [["view_count", 0], ["title", "abc"]]
[]
- or needs us to write a sql query
- where again
Question.where(["created_at > ? OR view_count = 0", 5.days.ago])
Question Load (0.6ms) SELECT "questions".* FROM "questions" WHERE (created_at > '2016-01-28 19:04:45.821922')
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
| 1 | My first question | my first question body | 2016-02-02 18:35:09 UTC | 2016-02-02 18:35:09 UTC | 0 |
| 2 | hello world! | what's up! | 2016-02-02 18:43:37 UTC | 2016-02-02 18:43:37 UTC | 1 |
| 3 | my second question | my second question body | 2016-02-02 18:46:48 UTC | 2016-02-02 18:46:48 UTC | 10 |
+----+--------------------+-------------------------+-------------------------+-------------------------+------------+
- search two conditions
Question.where(["title ILIKE ? OR body ILIKE ? ", "%my%", "%my%"])
Question Load (0.5ms) SELECT "questions".* FROM "questions" WHERE (title ILIKE '%my%' OR body ILIKE '%my%' )
- create 100 records
100.times { Question.create(title: Faker::Company.bs, body: Faker::Lorem.paragraph, view_count: rand(10)) }
- limit output, maybe also giving it an offset/frame
> Question.limit(10).offset(10)
Question Load (0.5ms) SELECT "questions".* FROM "questions" LIMIT 10 OFFSET 10
- update
q.update({body: "some new question body"})
(0.2ms) BEGIN
SQL (0.4ms) UPDATE "questions" SET "body" = $1, "updated_at" = $2 WHERE "questions"."id" = $3 [["body", "some new question body"], ["updated_at", "2016-02-02 19:38:35.821275"], ["id", 10]]
(1.2ms) COMMIT
destroy
q.destroy
(0.2ms) BEGIN
SQL (0.2ms) DELETE FROM "questions" WHERE "questions"."id" = $1 [["id", 10]]
(1.3ms) COMMIT
- create an index
bin/rails g migration add_indicies_to_questions
- modify the migration file
class AddIndiciesToQuestions < ActiveRecord::Migration
def change
# This will add an index (not unique) to the queestiosn table on the title column
add_index :questions, :title
add_index :questions, :body
# This will create a unique index
# add_index :question, :body, unique: true
# composite index
# or add_index :questions, [:title, :body]
end
end
- can put ruby code in seeds.rb
100.times do
Question.create title: Faker::Company.bs,
body: Faker::Lorem.paragraph,
view_count: rand(100)
end
bin/rake db:seed
-
put validations at the model level
-
avoid putting validations in the controller as much as possible
-
also can use jQuery to put in validation at the client level
-
edit the model
class Question < ActiveRecord::Base
#validation
#this will fail validations, so won't create or save,
# if the title is not provided
validates :title, presence: true
end
- and
> reload!
> q = Question.new
q = Question.new
+----+-------+------+------------+------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+-------+------+------------+------------+------------+
| | | | | | |
+----+-------+------+------------+------------+------------+
1 row in set
2.2.3 :006 > q.save
(0.2ms) BEGIN
(0.2ms) ROLLBACK
false
> q.errors
{
:title => [
[0] "can't be blank"
]
}
2.2.3 :004 > q.errors.class
ActiveModel::Errors < Object
2.2.3 :005 > q.errors.full_messages
[
[0] "Title can't be blank"
]
2.2.3 :006 > q.errors.full_messages.class
Array < Object
- unique value
validates :title, presence: true, uniqueness: true
> reload!
> q = Question.new(title: "My first question")
+----+-------------------+------+------------+------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+-------------------+------+------------+------------+------------+
| | My first question | | | | |
+----+-------------------+------+------------+------------+------------+
1 row in set
2.2.3 :010 > q.save
(0.2ms) BEGIN
Question Exists (0.5ms) SELECT 1 AS one FROM "questions" WHERE "questions"."title" = 'My first question' LIMIT 1
(0.2ms) ROLLBACK
false
- numericality
validates :view_count, numericality: {greater_than_or_equal_to: 0}
- format
validates :email, format: {with: /\A([\w+\-]\.?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i}
- customize valiation
# Custom validation method. must make sure that
#'no_monkey' is a method available for our class.
# The method can be public or private.
# it's more likely we will have it a private method because we
# don't need to use it outside this class
validate :no_monkey
private
def no_mokey
if title.downcase.include? "monkey"
errors.add(:title, "No monkey!!")
end
end
- change error messages
validates :body, uniqueness: {message: "must be unique!"}
q = Question.new(title: "title3333", body: "my first question body")
+----+-----------+------------------------+------------+------------+------------+
| id | title | body | created_at | updated_at | view_count |
+----+-----------+------------------------+------------+------------+------------+
| | title3333 | my first question body | | | |
+----+-----------+------------------------+------------+------------+------------+
1 row in set
2.2.3 :013 > q.save
(0.2ms) BEGIN
Question Exists (0.7ms) SELECT 1 AS one FROM "questions" WHERE LOWER("questions"."title") = LOWER('title3333') LIMIT 1
Question Exists (0.3ms) SELECT 1 AS one FROM "questions" WHERE "questions"."body" = 'my first question body' LIMIT 1
(0.2ms) ROLLBACK
false
2.2.3 :014 > q.errors
{
:body => [
[0] "must be unique!"
]
}
> q.valid?
Question Exists (0.4ms) SELECT 1 AS one FROM "questions" WHERE "questions"."title" IS NULL LIMIT 1
Question Exists (0.3ms) SELECT 1 AS one FROM "questions" WHERE "questions"."body" IS NULL LIMIT 1
false
- in model
#DSL, domain specific language
# args
# :body = first arugment,
# {} hash with validation type as the field, and value could be any attribute
# the code we use in here is complete valid RUby code bu the method naming
# and arguments are specific to ActiveRecord so we call this an Active Record DSL
validates( :body, {uniqueness: {message: "must be unique!"}})
- after_initialize
- before_validation
- after_validation
- before_save
- after_save
- before_commit
- fater_commit
after_initialize :set_defaults
before_save :capitalize_title
private
def set_defaults
self.view_count ||= 0
end
def capitalize_title
self.title.capitalize!
end
scope :recent, lambda{ order("created_at DESC").limit(5) }
scope :created_after, lambda{|x| where("created_at > ? ", x) }
#or
# def self.recent
# order("created_at DESC").limit(5)
# end
> Question.recent
> method_name = "title"
> q = Question.new(title: "abc", view_count=10)
> q.sent(method_name)
"abc"
- create a new controller
- in routes,
get "/questions/new" => "questions#new", as: :new_question
- define the new action in the controller file
class QuestionsController < ApplicationController
def new
@question = Question.new
end
end
- create a new erb, add a form_tag
<h1>New Question</h1>
<%# form_for takes in an activerecord object as a first argument, then it looks at the object. if the object is not persisted, the form will automatically use post for its method. it will also auto use 'questions_path' as its actions (convention is that the 'questions_path' will submit to the create action.) %>
<%= form_for @question do |f| %>
<%# f is form object %>
<div>
<%= f.label :title %>
<%= f.text_field :title %>
</div>
<div>
<%= f.label :body %>
<%= f.text_area :body %>
</div>
<%end%>
<div>
<%= f.submit %>
</div>
- in routes, add
post "/questions" => "questions#create", as: :questions
- when submitting this form, the "create" action is not found thus error is thrown
class QuestionsController < ApplicationController
def new
@question = Question.new
end
def create
# "question"=>{"title"=>"1", "body"=>"2"}
# @question = Question.new
# @question.title = params["question"]["title"]
#
# @question.body = params["question"]["body"]
# @question.save
# permit methods will only allow parameters that are explicitly listed, in this case :title and :body
# strong parameters
question_params = params.require(:question).permit([:title, :body])
question = Question.new question_params
question.save
# render text: params
end
end
- params show the following hash
- it separates the form values from the rest of the values (like authenticate tokens, etc)
"question"=>{"title"=>"1", "body"=>"2"}
- render
def create
# permit methods will only allow parameters that are explicitly listed, in this case :title and :body
question_params = params.require(:question).permit([:title, :body])
question = Question.new question_params
if question.save
render text: "Success"
else
# render is within the same request/response cycle
# This will render app/views/questions/new.html.erb template
# default behaviour is to render create.html.erb
render :new
# OR
# render "question/new"
# redirect makes a new request/response cycle
end
end
- Redirect is favoured after creating a product since it changes the url
# redirect_to question_path({id: @question.id})
# redirect_to question_path({id:@question})
# redirect_to @question
redirect_to question_path(@question)
- redirect_to will result in a 302 because 300 range is for redirect code
- get can re-use path from post
post "/questions" => "questions#create", as: :questions
# The path for this url is the same as the path for the create action (with POST)
# so just use the one defined for create, which is :questions_path
get "questions" => "questions#index"
- home page
root "questions#index"
- link to home page
<%= link_to "Home", root_path %>
- Display records and enable hyperlink. Edit the template and the controller for question:
<h1>All Questions</h1>
<ol>
<% @questions.each do |x| %>
<li><%= link_to x.title, question_path(x) %></li>
<% end %>
</ol>
def index
@questions = Question.all
end
- in new.html.erb, give it a form
<h1>Create New Product</h1>
<%#= @product.errors.full_messages.join %>
<ul>
<% @product.errors.full_messages.each do |m| %>
<li><%= m %></li>
<% end %>
</ul>
<%= form_for @product do |f| %>
<%# f is form object %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div>
<%= f.label :description %>
<%= f.text_area :description %>
</div>
<div>
<%= f.label :price %>
<%= f.text_field :price %>
</div>
<div>
<%= f.submit %>
</div>
<% end %>
####Edit
- routes
get "/products/:id/edit" => "products#edit", as: :edit_question
patch "/products/:id" => "products#update" => "products#update"
- create a new template
<%# form will use patch request if @product is persisted by adding hidden field
with name _method and value 'patch'.
Action URL = question_atph(@question) by convention
The form will prepopulate the fields with @question values %>
<%= form_for @product do |f| %>
<%# f is form object %>
<div>
<%= f.label :name %>
<%= f.text_field :name %>
...
the rest is the same as the form before
- and in controller
def update
@product = Product.find params[:id]
# strong param
product_params = params.require(:product).permit([:name, :description, :price])
# update automatically saves the record
@product.update product_params
redirect_to product_path(@product)
end
####Destroy
- in the show template where each individual product is displayed
<%= button_to "delete", @product, :method => :delete, data: {confirm: "Are you sure?"}%>
- or
<%= link_to "Delete", product_path(@product), method: :delete, data: {confirm: "Are you sure?"}%>
- in controller
def destroy @product = Product.find params[:id] @product.destroy redirect_to root_path end
before_action :find_question, only: [:show, :edit, :update]
# or
before_action :find_question, except: [:index]
- Partials have access to instance variables, eg. _form.html.erb.
- you pass local variables by passing a hash to the partial as in
<%= render "form", my_var: my_variable %>