Saturday, February 7, 2015

Modelling Categories

Earlier I talked about how I'm going to start with three models: Users, Categories, and Pages. I got the basics of Users done in the last post, so let's do Categories and Pages now. Each Page is going to have one and only one Category, while a Category will obviously have many Pages. Here is my starting point for these models:

A Category is going to have these attributes:

  • a name,
  • the order of its appearance on the list (using an integer for now),
  • a flag to determine if it's should be private to the user,
  • a short description of the category.
So, we have
  t.string  :title
  t.integer :order
  t.boolean :private
  t.text    :description
Create the Category model using the following scaffold:
$ rails generate scaffold Categories title:string order:integer:index private:boolean description:text

A Page has the following attributes:

  • a name, order, and a boolean flag for private entries
  • the category it belongs to
  • the contents
How are we going to model the contents though? Is everything going to be text? Can we put pictures? PDFs?

I'm going to start by just putting text inside the contents. However, if we have something like t.text :contents, i.e. putting the whole article into one text entry in the database, then every time we need to edit the entry we need to trawl through the whole article. It might make more sense to split the contents into smaller chunks.

I'm therefore going to create a new model called Section as follows:

  t.belongs_to :page, index: true
  t.string  :title
  t.integer :order
  t.text    :content

  t.integer :page_id
I'm not sure if I need a boolean flag for private sections, but this can always be added later. The belongs_to variable tells rails that each Page has many Sections. The :page_id variable gets used by the foreigner gem to set a database level foreign key (something I prefer).

Similarly, the migration for Page is

  t.belongs_to :category, index: true
  t.string   :title
  t.integer  :order
  t.boolean  :private
  
  t.integer :category_id

What's left is to link Sections to Pages and Pages to Categories. In app/models/section.rb
  class Section < ActiveRecord::Base
    belongs_to :page
  end
and in page.rb and category.rb
  class Page < ActiveRecord::Base
    belongs_to :category
    has_many :sections
  end

  class Category < ActiveRecord::Base
    has_many :pages
  end   

Tuesday, December 9, 2014

Creating user model

I'm going to start by creating a model for users, using devise for authentication. Do give the GitHub page a read to get some idea on devise. I'm mostly just going to be following the Getting Started section here.

To install devise, put the gem in the Gemfile,

  gem 'devise'
and then use
  rails generate devise:install
  rails generate devise User
The installer is going to ask you to set up the mailer, which I will do at the end of this blog post. The next thing to do is to check the generated app/models/user.rb and migration file, and make any changes that you think is necessary. I'm happy to just use the default User model generated by devise. For this application, I'm going to use
  class User < ActiveRecord::Base
    devise :database_authenticatable, 
           :registerable, :confirmable, 
           :lockable, :recoverable, 
           :rememberable, :trackable, :validatable
For the migration file (inside db/migrate), I uncommented the variables for lockable and confirmable.


Creating a welcome page

I'm going to need a welcome page to test out devise, so let's make one quickly using

  rails generate controller StaticPages start
which is going to create start.html in the app/views/static_pages directory.
When I started coding with rails, I didn't know why you would refer to an html as a static page. Michael Hartl, for one, talked about "mostly static pages". Back then I thought every page was static. After all, aren't websites simply a collection of htmls?

After some time learning about rails, and more generally about MVC (Model View Controller) framework, I understand now that a rails app focuses on the models. If we generate a model using scaffolding, rails will automatically generate several html pages, or views, to let us interact with the model. For example, it will create an index.html page that lists every instance of a model. Most of these pages would be dynamically created based on the information stored in the database.

So an html page which shows a welcome message and login/password box is pretty static in comparison. It seems like a good idea to group all the static pages that you're going to write under one controller, because they mostly behave in the same way. This is what I did above, using the StaticPages controller.

It is possible to set up a master layout for all pages under one controller in rails: inside the app/views/layouts directory, put an html (or html.erb) file with the same name as the model. I'm going to be using slim, so it's .html.slim from here on. The master layout for all StaticPages views can be placed in

  app/views/layouts/static_pages.html.slim
You can also put the layout inside application.html.slim which becomes the fallback layout file, i.e. it will be used if an html with the same name as the model is not found. You can do more complicated things involving layouts inside the controller; check the rails guides.

I put the following code inside static_pages.html

doctype html
html
  head
    title "Insert title"
    = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true
    = javascript_include_tag 'application', 'data-turbolinks-track' => true
    = csrf_meta_tags
    .container style="text-align: right"
      - if user_signed_in?
        h5 Logged in as #{current_user.username} | #{link_to "Log out", destroy_user_session_path, method: :delete}
      - else
        = link_to "Log in", new_user_session_path

  body
    .container
      = yield
Notice how it already contains several devise helpers, like current_user, user_signed_in?, etc.


Setting up the mailer

Devise provides a login page, namely new_user_session_path, where a user can also sign up by providing an email and password. As usual, an email is then sent to the user's email address to confirm its authenticity by providing a confirmation link. For this system to work, we must set up a mail system in our development environment. I use MockSMTP.

MockSMTP provides a local SMTP server using port 1025. Once it is installed (it is a paid app by the way), add the following codes to config/environments/developmentr.rb

config.action_mailer.default_url_options = {:host => 'localhost:3000'}
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {:address => "localhost", :port => 1025}
Registration by email should work properly. If you try to sign up, an email will be generated and sent to your MockSMTP mailbox, from where you can access the confirmation link. To see the page locally, we need to run both db:create and db:migrate

Monday, December 8, 2014

Setting up rails

Unfortunately I only know how to do this on Mac. I have been wanting to set up a Windows development environment for a while, but it just doesn't seem to be worth the time or effort. I have also installed a few other things like git, heroku, and maybe a few other useful things that I can't think of right now. Anyway, this post is not about how to install things. It is a reminder for me of what I should use to keep things updated.

I first learned rails by reading Michael Hartl's tutorial, so just about everything I do here follows his instructions. If you know nothing about rails, then the rest of the blog won't be useful to you.

To set up rails, we must first install Ruby. I use rvm (see page for installation instruction). Once it is installed, keep it updated using

  rvm get stable
and then install the required dependencies for Ruby using (you need to have Xcode installed)
  rvm requirements
There is a cheat sheet here that lists the most important commands. The ones I have used are
  rvm list known
  rvm install 2.1.5
  rvm use --default 2.1.5 
The last line sets it up so that version 2.1.5 is used for current and future sessions.


Installing rails

To install rails, use

  gem install rails 
... and that's it!


Rails Quickstart

To start a new project:

  rails new <appname> --skip-test-unit --skip-bundle
where appname is the name of your application. I skip test-unit because I have never used it, and I prefer to write my own tests ( ... this may not happen in this project).

Once rails create the directory and necessary files, I copy and paste the following files from previous projects:

  1. /.gitignore -- list of files to be ignored by git (e.g. swap files, database.yml)
  2. /config/database.yml -- database setting, modify it after copying
  3. Gemfile -- list of add ons used for the rails project

As you can see, I use GitHub to store my code. To use git, do the necessary installation, then start a repository via the website. Link your local directory using the instruction provided, i.e.

  git init
  git remote add origin <git address>
Here are the commands to add all untracked files, commit, and the push. By the default,
  git add -A
  git commit -m "Initial commit"
  git push origin master
It might be a good idea to run bundle before pushing, just to make sure all the gems work.

For deployment, I use Heroku, since this makes the whole process amazingly simple! You need to create an account, then download Heroku toolbelt from here and install it. Once this is done, link the app to Heroku using

  heroku login
  heroku apps:create  --stack cedar
Heroku creates a remote location named 'heroku' where you can push your code to, so to deploy your app, use
  git push heroku master
It might be a good idea to push to heroku and see if it can be deployed now (even though it won't do anything). To view the site locally, run the rails server using the command
  rails server -p <port number>
and then open the site at http://localhost:<port number>, which should give you a welcome message.

Saturday, November 29, 2014

Planning stage

How to model the repository?

My starting point is to create a database model called Pages to hold the contents, with the following migration:
  class CreatePages < ActiveRecord::Migration
    def change
      create_table :pages do |t|
        t.string       :title
        t.text         :abstract
        t.text         :content
      end
    end
which I think is pretty self-explanatory. (Note: if you don't read rails, it basically creates a database table with three columns, title, abstract, and content, with string and text datatypes). Do I need another model for the categories? A category may not have much, or any, contents. It might be nice to have a little bit of abstract to say what it is about, but that's about all it needs. So it does look like I can represent them using the same Pages object, maybe by adding a boolean column to note that it is a category. 

However, when the webpage loads, it should display all the categories on the left hand side. If I keep all the categories in the same table as the Pages objects, it may slow things down once there is a lot of contents. I don't think it's very efficient having to scan the 'Page' table to pick out which ones are the categories, even when it is indexed. Plus, I do not see any benefit, unless I want to demote a category to a Pages object, or vice versa, and that is not something I should be doing anyway.

How about sub-categories? These will only be shown when a category is selected, and it is possible that I want to convert a Pages object into a sub-category of its own. So, I'll represent them using the Pages model.

Now, more importantly, how do you link Categories to Pages? Fortunately there is a gem (i.e. plugin) for this called Ancestry.

Should I add users?

At this point, there's only going to be one user in mind, me, but it does not take much effort to add users on the application using Devise. Each user should be able to choose which Pages and Categories are private, and displayed to everyone. I imagine a url structure like the following:
    http://<domain>/Users/1/Categories/1/Pages/1

So up till now we have the following three data models Users, Categories, and Pages.

Friday, November 28, 2014

Start of the blog

Here is the blog that I plan to keep as I build my note-taking web application using Ruby on Rails. 

My goal application is a web-based repository for all my notes, organised by categories (and sub-categories), with all the entries stored in a database. The closest analogy is like writing a book using LaTeX, with chapters, sections, and subsections. I want the chapters and sections displayed clearly at all times so that I can jump around easily. I imagine it to look something like this:


The categories are listed in the tab on the left hand side, and can be expanded to show all sub-categories and/or entries. The plan is to have a tree structure three levels deep --- category, sub-category, and content --- though I'd like to model the entries so that this can be further expanded, e.g. by using the same model to represent a category and sub-category so that one can be converted to the other, or so that adding sub-sub-category can be done easily.

Why not use an existing solution?

I have been using DevonThink in the past, but it is Mac-only, and I want something online-based. Evernote was too disorganised for me, or rather, I'm too disorganised for Evernote. TiddlyWiki was great, but I need more structure than a wiki. I have tried a few more tools, but haven't found anything suitable so far. 

Writing something from scratch also gives me more freedom than any tools I can find, particularly since I hope to be able to process LaTeX entries in the future (none of the above supports LaTeX as far as I know). Things like code highlighting can also be done easily in html. 

I plan to be using Ruby on Rails, since this is what I have been learning for the last year. I may consider using other tools that I have learned (AngularJS, node.js), but after all this is a beginner's project, so I'd like to keep it simple.

Why keep a blog?


I like keeping records on everything that I do, so that I can always refer back to it (hence the project!). It doesn't take that much effort to convert it to a blog, and maybe someone can benefit from reading my drivels as well.