lakesare / LocomotiveCMS-tutorial

Basic introduction to LocomotiveCMS features for Rails developers.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Starting out with LocomotiveCMS.

Gems

LocomotiveCMS uses Carrierwave gem for attachment management. LocomotiveCMS uses MongoDB as its database, and its datamapper (Object-Document Mapping) is Mongoid as an alternative for usual ActiveRecord library that is used as an ORM (Object-Relational Mapping) for relational databases correspondingly (MySQL, PostgreSQL). Still, Mongoid uses some parts of ActiveModel - eg Mongoid includes ActiveModel::Validations to provide us with usual validates_presence_of :name syntax.

MongoDB essentials

MongoDB is a NoSQL database. After you install it, you should start a server with mongod. Then you will be able to access a console with mongo. There are many types of NoSQL databases: key-value (Redis, CouchDB), column (Cassandra), document-oriented (MongoDB), and a few more. MongoDB is a document-oriented db and it uses a different from SQL databases syntax for similar concepts. For example, what we call a document in MongoDB would be called a row in SQL database. You can learn about other terminology parallels here: http://docs.mongodb.org/manual/reference/sql-comparison/.

Why use new terminology (collection vs. table, document vs. row and field vs. column)? Is it just to make things more complicated? The truth is that while these concepts are similar to their relational database counterparts, they are not identical. The core difference comes from the fact that relational databases define columns at the table level whereas a document-oriented database defines its fields at the document level. That is to say that each document within a collection can have its own unique set of fields.

Some other interesting things about MongoDB:

  • it doesn't support table joins. dont be very afraid to store repeated data.
  • given that collections don’t enforce any schema, it's entirely possible to build a system using a single collection with a mishmash of documents (but it would be a very bad idea)
  • it doesn't support transactions

more on mongodb: https://www.mongodb.com/blog/post/thinking-documents-part-1?jmp=docs&_ga=1.54024672.951253070.1435561573

links used: http://openmymind.net/mongodb.pdf

MongoDB console

To get an idea on how it works in practice, input use hi in your mongo console. It doesn't matter that the database doesn't really exist yet. The first collection that we create will also create the actual 'hi' database. To list collections (==tables) in our 'hi' database, input db.getCollectionNames(). Since we have no tables, this should return []. To create a collection, we can just create a new document, and this will automatically create its table. So, to create unicorns database with a new record, use:

db.unicorns.insert({name: 'Aurora', age: 200}) #will create a collection unicorns 
#unless it's already created and create a document with fields 'name' and 'age'

Argument passed to the insert command is in a JSON format. Internally MongoDB uses BSON (Binary JSON) to store data.

db.unicorns.find() #will return all documents from unicorns collection

will return all documents (==rows) unicorns collection (==table) currently has.

You may notice that documnent returned after executing db.unicorns.find() has an _id field in it: { "_id" : ObjectId("559881b62b9a63424d1c43d6"), "name" : "Aurora", "age" : 200 }. It was created automatically to provide a document with unique identifier.

If we execute db.unicorns.insert({home: 'forest'}) now, we will see by db.unicorns.find() that this document was gladly added even though its fields (==columns) are completely different from the previous record.

db.unicorns.remove({}) #will delete all documents from unicorns collection
db.unicorns.remove({name: 'evelyn'}) #will delete records with name: 'evelyn'

Mongoid

Follow this tutorial to get a feeling on how to work with Mongoid gem: http://railscasts.com/episodes/238-mongoid. Mongoid supports both relational-style associations (through post_id, eg) and embedded associations. However, it's not recommended to use relational associations extensively. What embedded association basically is, is a document embedded in another document. You could think it's a bad style from relation database point of view, but it's actually a preferred way of organizing a document-oriented db. The reasoning behind it is as follows:

  • MongoDB provides no transactions, and no join support, so it is impossible to ensure that the database is kept in a consistent state with regards to referencing documents from one collection to another
  • without support for joins, you will find that the number of database queries executed grows in order to retrieve documents with their associations.

Data modeled with these classes:

class Article
      field :title #defaults to ", type: String"
      field :published_on, type: Date
  embeds_many :comments
end

class Comments
      field :content
  embedded_in :article, :inverse_of => :comments
end

may look like this upon retrieval (of Article instance):

{
title: 'Apples',
published_on: Fri, 10 Jul 2015
comments: [
            { content: 'are good' },
            { content: 'are bad' }
          ]
}

Valid types for fields somewhat correspond to Ruby's classes: ["Array", "BigDecimal", "Boolean", "Date", "DateTime", "Float", "Hash", "Integer", "BSON::ObjectId", "Moped::BSON::Binary", "Range", "Regexp", "String", "Symbol", "Time", "TimeWithZone"]. So that if you'd like your field to be of type Text, use String type as it is of variable length, just like in Ruby.

As already mentioned, MongoDB supports dynamic fields - if in your console you'll write

db.unicorns.insert({home: 'forest'})

it will automatically create a home field for unicorn collection. Mongoid supports dynamic attributes out of the box as well, so that if we try:

Article.find_by(title: 'Apples')[:gender] = 'Female'

, an article with gender 'Female' will be initialized. To save it, use usual article.save. This will, however, not work with .gender accessor.

A few comparisons of ActiveRecord vs Mongoid methods:

ActiveRecord Mongoid
Article.all => ActiveRecord::Relation object, that will list all rows Article.all => Mongoid::Criteria object. It's lazily evaluated (only touches db when needed), so to get all documents, use, eg, Article.all.to_a
`Article.all.each { article
Article.find(12), will raise an error if record was not found Article.find(12), same
Article.find_by(title: 'Apples') Article.find_by(title: 'Apples') similar as ActiveRecord's find_by (finds by title), but will raise an error if record was not found
Article.where(title: 'Apples') Article.where(title: 'Apples')
Article.create(gender: 'female') will init and persist a row if gender column exists in this table Article.create(gender: 'female') will init and persist a document even if there is no predefined field 'gender' in this collection
article.update_attributes alias or #update article.update_attributes updates attributes with validations and callbacks.
article.destroy deletes row, runs destroy callbacks article.destroy, same

LocomotiveCMS structure

In LocomotiveCMS, model is called a Content Type. LocomotiveCMS mimics a usual Rails application. I allows to create a blog through user interface alone, where following analogies could be drawn:

Usual Rails App LocomotiveCMS interface
Model Content Type
Instance of a model, a row Content Entry
Fields user creates themselves (not _id, created_at, ...) Custom Fields

In reality though, every 'model' imitation created by us is an instance of ContentType123 (where 123 is a random number) class, and every 'entry' of that model is an instance of ContentEntry567 class. If we look at ContentType123.ancestors, we'll see that this class inherits from, expectedly, ContentType class, inheriting all its class and instance methods. To see which methods are available on it, we can look at the locomotive_cms engine source code: gem which locomotive_cms will give this engine's location in your pc, and in app/models/locomotive/content_type.rb you'll find a Locomotive::ContentEntry class.

You may notice there this line:

module Locomotive
  class ContentType
    has_many :entries, class_name: 'Locomotive::ContentEntry', dependent: :destroy
  end
end

which gives an idea on how we get an illusion of model - model instance relationship.

Working with source

About

Basic introduction to LocomotiveCMS features for Rails developers.