Ruby on Rails: Rails 3 (part 3: other stuff/notes, partials etc.)

Layouts

Folder app/views contains also layout folder for storing
generic (application-specific) layouts. It contains htmls, headers, body etc.
Uses following code to insert result of processing views.

<%= yield =>

app
├── assets
├── controllers
├── helpers
├── mailers
├── models
└── views
    ├── home
    ├── layouts
    │   └── application.html.erb
    └── posts

ActveRecords

ActiveRecords used in example;
Basic methods used to create/return object Post. All methods are inherited from ActiveRecords
by model Post (app/models/post.rb) and executed via this model.

  • @posts = Post.all (retrieves everything from database)
    SELECT "posts".* FROM "posts"
  • @post = Post.find(params[:id])
    (finds posts in DB, e.g. post.find(2) executes SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 2]])
  • @post = Post.new (creates new object w/o writing to DB)

Most often used methods on already existing object Post
(@post = already existing Post object):

  • post.save - saves into DB (INSERT)
  • post.errors - lists last errors
  • post.update_attributes (UPDATE)
  • post.destroy - deletes posts from DB (DELETE)
  • Post.count - lists all posts (e.g. SELECT COUNT (*) FROM "posts")

All above can be tried in rails console

Partials

Partials allow to render for example forms and other stuff.
Rendering is executed from views: e.g. app/views/posts/new.html.erb.
Following code <%= render 'form' %> calls to render file view/posts/_form.html.erb.

More on here: http://guides.rubyonrails.org/layouts_and_rendering.html#using-partials

Creating next model

This step I followed as instructed in getting started guide from RubyOnRails.org.

>>generate model Comment commenter:string body:text post:references
      invoke  active_record
      create    db/migrate/20111105130626_create_comments.rb
      create    app/models/comment.rb
      invoke    test_unit
      create      test/unit/comment_test.rb
      create      test/fixtures/comments.yml
 
>>rake db migrate
==  CreateComments: migrating =================================================
-- create_table(:comments)
   -> 0.0021s
-- add_index(:comments, :post_id)
   -> 0.0006s
==  CreateComments: migrated (0.0031s) ========================================

Let's see changes in databases:

>>rails dbconsole
.table
comments           posts              schema_migrations
 
.dump comments
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE "comments" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "commenter" varchar(255), "body" text, "post_id" integer, "created_at" datetime, "updated_at" datetime);
CREATE INDEX "index_comments_on_post_id" ON "comments" ("post_id");
COMMIT;
 
PRAGMA table_info(comments);
cid   name           type  notn  dflt  pk           
----  -------------  ----  ----  ----  -------------
0     id             INTEGER  1           1            
1     commenter      varchar(255)  0           0            
2     body           text  0           0            
3     post_id        integer  0           0            
4     created_at     datetime  0           0            
5     updated_at     datetime  0           0    
 
sqlite> .dump posts
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE "posts" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(255), "title" varchar(255), "content" text, "created_at" datetime, "updated_at" datetime);
INSERT INTO "posts" VALUES(1,'Abc','Def','ASd','2011-11-04 16:55:37.451348','2011-11-04 16:55:37.451348');
INSERT INTO "posts" VALUES(2,'aSdasds aaaAAAAA','ASDFA','adfadf','2011-11-04 16:56:15.750056','2011-11-04 16:56:15.750056');
INSERT INTO "posts" VALUES(3,NULL,NULL,'A new post','2011-11-05 09:31:41.600505','2011-11-05 09:31:41.600505');
INSERT INTO "posts" VALUES(4,'vito','some title here','abc','2011-11-05 09:41:15.868079','2011-11-05 09:41:15.868079');
COMMIT;
 
sqlite> PRAGMA table_info(posts);
cid   name           type  notn  dflt  pk           
----  -------------  ----  ----  ----  -------------
0     id             INTEGER  1           1            
1     name           varchar(255)  0           0            
2     title          varchar(255)  0           0            
3     content        text  0           0            
4     created_at     datetime  0           0            
5     updated_at     datetime  0           0        

We can see execution of creation of generate model Comment commenter:string body:text post:references created new table comments, with columns:
1|commenter|varchar(255)|0||0
2|body|text|0||0
3|post_id|integer|0||0|

and automatic fields we didn't ask for:
0|id|INTEGER|1||1
4|created_at|datetime|0||0
5|updated_at|datetime|0||0

Additionally to creation of table with PRIMARY key ID we see relation between
posts and comments tables above:
CREATE INDEX "index_comments_on_post_id" ON "comments" ("post_id")

It seems Comments are automatically connected to Post in Rails:

>cat app/models/comment.rb
class Comment < ActiveRecord::Base
  belongs_to :post
end

Creating relation one post -> multiple comments (model level)

Model Comment already indicates it belongs to post;
By adding:

has_many :comments
into class Post (post.rb in models)
we add extra functionality (retrieving posts from database will retrieve
automatically all related comments).

More about it is supposed to be here: http://guides.rubyonrails.org/association_basics.html
(way more...)

Checking access to comments via Post objects

Lets check this automatic retrieving posts... Database has post with ID=1.
Following code creates two comments with corresponding id = 1 (post_id=1 in Comments table)

>> rails console
 
> a = Comment.new()
> a.commenter = "Witold"
> a.body = "Hello - this is a test"
> a.post_id = 1
> a.save
  SQL (94.8ms)  INSERT INTO "comments" ("body", "commenter", "created_at", "post_id", "updated_at") VALUES (?, ?, ?, ?, ?)  [["body", "Hello - this is a test"], ["commenter", "Witold"], ["created_at", Sat, 05 Nov 2011 14:14:54 UTC +00:00], ["post_id", 1], ["updated_at", Sat, 05 Nov 2011 14:14:54 UTC +00:00]]
 
> b = Comment.new(:commenter => "Witold", :body=>"Second comment", :post_id => 1)
> b.save
  SQL (1.5ms)  INSERT INTO "comments" ("body", "commenter", "created_at", "post_id", "updated_at") VALUES (?, ?, ?, ?, ?)  [["body", "Second comment"], ["commenter", "Witold"], ["created_at", Sat, 05 Nov 2011 14:17:57 UTC +00:00], ["post_id", 1], ["updated_at", Sat, 05 Nov 2011 14:17:57 UTC +00:00]]
 
# Now.. lets retrieve all posts:
first_post = Post.find(1)
  Post Load (65.5ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 1]]
 => #<Post id: 1, name: "Abc", title: "Def", content: "ASd", created_at: "2011-11-04 16:55:37", updated_at: "2011-11-04 16:55:37"> 
 
# But how to retrieve comments? - it seems we don't have mechanism for that...
>> first_post.co
first_post.column_for_attribute      first_post.connection_handler?       first_post.content_change
first_post.committed!                first_post.content                   first_post.content_changed?
first_post.configurations            first_post.content=                  first_post.content_was
first_post.connection                first_post.content?                  first_post.content_will_change!
first_post.connection_handler        first_post.content_before_type_cast  

From above it is clear we are missing something; Let's install controller
with corresponding name Comments:

rails generate controller Comments

And now... - back to Rails

>>rails console
> a = Post.find(1)
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 1]]
 => #<Post id: 1, name: "Abc", title: "Def", content: "ASd", created_at: "2011-11-04 16:55:37", updated_at: "2011-11-04 16:55:37"
 
> a.comments
  Comment Load (0.5ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1
 => [#<Comment id: 1, commenter: "Witold", body: "Hello - this is a test", post_id: 1, created_at: "2011-11-05 14:14:54", updated_at: "2011-11-05 14:14:54">, #<Comment id: 2, commenter: "Witold", body: "Second comment", post_id: 1, created_at: "2011-11-05 14:17:57", updated_at: "2011-11-05 14:17:57">] 

Yupi!! It worked.!

Creating comments for second post

> b = Post.find(2)
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 2]]
 => #<Post id: 2, name: "aSdasds aaaAAAAA", title: "ASDFA", content: "adfadf", created_at: "2011-11-04 16:56:15", updated_at: "2011-11-04 16:56:15"> 
 
# No comments with post 2:
> b.comments
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 2
 => [] 
 
# Add new comments:
> b.comments.create(:body => "This is a second test", :commenter => "Witold Kaczurba")
  SQL (0.9ms)  INSERT INTO "comments" ("body", "commenter", "created_at", "post_id", "updated_at") VALUES (?, ?, ?, ?, ?)  [["body", "This is a second test"], ["commenter", "Witold Kaczurba"], ["created_at", Sat, 05 Nov 2011 15:05:22 UTC +00:00], ["post_id", 2], ["updated_at", Sat, 05 Nov 2011 15:05:22 UTC +00:00]]
 => #<Comment id: 6, commenter: "Witold Kaczurba", body: "This is a second test", post_id: 2, created_at: "2011-11-05 15:05:22", updated_at: "2011-11-05 15:05:22"> 

Now deleting all comments from post 2:

> b.comments.destroy_all
  SQL (0.4ms)  DELETE FROM "comments" WHERE "comments"."id" = ?  [["id", 6]]
 => [#<Comment id: 6, commenter: "Witold Kaczurba", body: "This is a second test", post_id: 2, created_at: "2011-11-05 15:05:22", updated_at: "2011-11-05 15:05:22">] 

As simple as it is!!!

Automatic delete of comments of post that was removed

In post model (app/models/post.rb) following should be modified:

  has_many :comments

To the:
  has_many :comments; :dependent => :destroy;

Checking if it works:

>> rails console
> Post.find(4).comments
  Post Load (0.3ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 4]]
  Comment Load (0.7ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 4
 => [#<Comment id: 8, commenter: "", body: "", post_id: 4, created_at: "2011-11-05 15:12:39", updated_at: "2011-11-05 15:12:39">, #<Comment id: 9, commenter: "New comment", body: "Hello...ZZZ...", post_id: 4, created_at: "2011-11-05 15:12:47", updated_at: "2011-11-05 15:12:47">] 
> Post.find(4).comments[0].id
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 4]]
  Comment Load (0.4ms)  SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 4
 => 8 
 
> Comment.find(8)
  Comment Load (0.2ms)  SELECT "comments".* FROM "comments" WHERE "comments"."id" = ? LIMIT 1  [["id", 8]]
 => #<Comment id: 8, commenter: "", body: "", post_id: 4, created_at: "2011-11-05 15:12:39", updated_at: "2011-11-05 15:12:39"> 
 
# Check if deletion of 4th post deletes also comment with id=8:
> Post.find(4).destroy
  Post Load (0.2ms)  SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1  [["id", 4]]
  SQL (0.4ms)  DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 4]]
 => #<Post id: 4, name: "vito", title: "some title here", content: "abc", created_at: "2011-11-05 09:41:15", updated_at: "2011-11-05 09:41:15"> 
 
> Comment.find(8)
  Comment Load (0.3ms)  SELECT "comments".* FROM "comments" WHERE "comments"."id" = ? LIMIT 1  [["id", 8]]
 => #<Comment id: 8, commenter: "", body: "", post_id: 4, created_at: "2011-11-05 15:12:39", updated_at: "2011-11-05 15:12:39"> 

Hm.... Didn't really work :( the way they described it...