Using Mongoid with Ruby on Rails

Post on 16-Apr-2017

620 views 1 download

Transcript of Using Mongoid with Ruby on Rails

Schema-less Design An introduction to the Mongoid ODM

1. MongoDB

2. Mongoid ODM compares to ORM/SQL

3. Modelling Bitmaker Rainforest app without ActiveRecord

4. Going further

5. Conclusion

Overview

Questions going in• What is MongoDB?

• How does it compare from SQL-based databases?

• What does schema-less entail?

• How does ODM compare with ORM? Rainforest tutorial

• Worth it?

Object Document Mapper (ODM)

Translate between objects in code and document representation of data.

Object Relational Mapping translates between objects in code the the relational representation of the data.

MongoDB• Open-source database

• NoSQL (cluster friendly, 21st-century web, non-relational, schema-less)

• Data is stored in collections and documents whereas in SQL data is stored in tables and rows

• JSON-like structure to documents

Differs from SQL

No JOINS -> run two or more queries

Embed and Referencing of document/data objects

Fast querying

PostgreSQL has strict schema / MongoDB has implicit schema

Use cases

“Big Data” - large and unwieldy

90% of business data is unstructured

Increasing volume, variety and velocity

craigslist, Forbes, New York Times, Foursquare, etc.

The SetupInstall mongodb

https://docs.mongodb.org/manual/installation/

Run mongo server and mongo database in separate terminal tabs

~>rails new rainforest —skip-active-record

The Setup

Removes ActiveRecord from the application No schema.rb No migrations folder

gem 'mongoid', '~> 5.1'

~>bundle install

~>rails generate mongoid:configcreates config/mongoid.yml

application.rb

require File.expand_path('../boot', __FILE__)

require "rails" # Pick the frameworks you want: require "active_model/railtie" require "active_job/railtie" # require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" require "sprockets/railtie" require "rails/test_unit/railtie"

. . .

mongoid.yml

development: # Configure available database clients. (required) clients: # Defines the default client. (required) default: # Defines the name of the default database that Mongoid can connect to. # (required). database: rainforest_development # Provides the hosts the default client can connect to. Must be an array # of host:port pairs. (required) hosts: - localhost:27017 options: # Change the default write concern. (default = { w: 1 }) # write: # w: 1

Rainforest Appclass Product include Mongoid::Document field :name, type: String field :description, type: String field :price_in_cents, type: Integer . . .

embeds_many :reviews embeds_one :category end

class Category include Mongoid::Document field :name, type: String

embedded_in :product end

Product Controller

class ProductsController < ApplicationController . . . def product_params params.require(:product).permit(:name, :description, :price_in_cents, {category: [:category_id, :name]}) end end

Rainforest Appclass User include Mongoid::Document field :name, type: String field :email, type: String field :password, type: String . . . has_many :products

end

class Review include Mongoid::Document field :comment, type: String

field :user_id, type: BSON::ObjectId . . . embedded_in :product end

RelationsMongoid ODM associations similar to ActiveRecord ORM

Association is not just for Relational Mappers

Embeds One, Embeds Many, Has One, Has Many, Has And Belongs To Many, counter_cache (cache the counts of an associated model)

No has_many :through

BSON Objects

BSON: supports embedding of objects within arrays

Allow for extensions not part of JSON spec

Minimum overhead

Traverse quickly (fast to encode and decode)

{"_id" : ObjectId("5728b57b370d93124e000000"),"name" : "Day planner","description" : "Schedule events","price_in_cents" : 1299,"user_id" : ObjectId("5727b3b0370d93009d000000"),"category" : {

"_id" : ObjectId("5728b57b370d93124e000001"),"name" : “Office material"

},"reviews" : [

{"_id" : ObjectId("5728b5a3370d93124e000002"),"comment" : "A great product","user_id" : ObjectId("5727b3b0370d93009d000000")

}

[1] pry(main)> Product.first#<Product:0x007fc1290cd108> { :_id => BSON::ObjectId('56b54bc3370d93d2d5411757'), :category => { "_id" => BSON::ObjectId('570c78f1370d936468000000'), "name" => "Electronics" }, :description => "Listen to music without all the hassle of a cord.", :name => "Wireless Headphones", :price_in_cents => 60, :reviews => [ [0] { "_id" => BSON::ObjectId('56b54e59370d93d3b4fe9848'), "comment" => "Test comment”,

"user_id" => BSON::ObjectId('56eb9d92370d937950d21463 }, [1] { "_id" => BSON::ObjectId('56b54e5f370d93d3b4fe9849'), "comment" => "Checking comment\r\n”, "user_id" => BSON::ObjectId('56eb9d92370d937950d21463 }, [2] { "_id" => BSON::ObjectId('56b54e6d370d93d3b4fe984a'), "comment" => "Just bought these headphones.”, "user_id" => BSON::ObjectId('56eb9d92370d937950d21463 }, [0] { "_id" => BSON::ObjectId('5706dc66370d934ccd000000'), "comment" => "Checking embedded review", "user_id" => BSON::ObjectId('56eb9d92370d937950d21463') } ]}

Rails Console[3] pry(main)> products = Product.all#<Mongoid::Criteria selector: {} options: {} class: Product embedded: false>

The query doesn’t run until the data is requested

[6] pry(main)> products.each {|product|puts product.name }Wireless HeadphonesChromecast for TVFerrariATMComputerFerrari2Game BoySony HeadphonesSpeakersCarDell ComputerSamsung TelevisionAudi ConvertibleSnowmobile#<Mongoid::Contextual::Mongo:0x007fc129167550 @cache=nil, @klass=Product, @criteria=#<Mongoid::Criteria selector: {} options: {} class: Product embedded: false>, @collection=#<Mongo::Collection:0x70233797373000 namespace=rainforest_development.products>, @view=#<Mongo::Collection::View:0x70233797372780 namespace='rainforest_development.products @selector={} @options={}>, @cache_loaded=true>

Implicit schema

[9] pry(main)> product[:model] = "TS2-3Q78"

[8] pry(main)> product = Product.first

[1] pry(main)> Product.first#<Product:0x007fc1290cd108> { :_id => BSON::ObjectId('56b54bc3370d93d2d5411757'), :category => { "_id" => BSON::ObjectId('570c78f1370d936468000000'), "name" => "Electronics" }, :description => "Listen to music without all the hassle of a cord.", :model => "TS2-3Q78", :name => "Wireless Headphones", :price_in_cents => 60, :reviews => [ [0] { "_id" => BSON::ObjectId('56b54e59370d93d3b4fe9848'), "comment" => "Test comment" }, [1] { "_id" => BSON::ObjectId('56b54e5f370d93d3b4fe9849'), "comment" => "Checking comment\r\n" }, [2] { "_id" => BSON::ObjectId('56b54e6d370d93d3b4fe984a'), "comment" => "Just bought these headphones." }, [3] { "_id" => BSON::ObjectId('5706dc66370d934ccd000000'), "comment" => "Checking embedded review", "user_id" => BSON::ObjectId('56eb9d92370d937950d21463') } ]}

Mongoid

Similar syntax to ActiveRecord - associations, datatypes, etc.

No need for migrations

Great documentation; active community

Popular gems have mongoid counterpart (Devise, carrierwave-mongoid, mongoid-paperclip, geocoder, mongoid-rspec)

Setup is fast and mistakes quickly fixed (no rollbacks)

class Product include Mongoid::Document include Mongoid::Paperclip . . .

has_mongoid_attached_file :avatar, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: lambda { |image| ActionController::Base.helpers.asset_path('missing.png') }

validates_attachment_content_type :avatar,:content_type => ["image/jpg", "image/jpeg", "image/png", "image/gif"]

end

class ProductsController < ApplicationController . . . def product_params params.require(:product).permit(:name, :description, :price_in_cents, :avatar, {category: [:category_id, :name]}) end

#<Product:0x007fc12b0db148> { :_id => BSON::ObjectId('570c32f9370d935b78000000'), :avatar_content_type => "image/jpeg", :avatar_file_name => "snowmobile.jpeg", :avatar_file_size => 7081, :avatar_fingerprint => "33234414813b3e3bf8a26281a2d7316f", :avatar_updated_at => 2016-04-12 00:56:31 UTC, :category => { "_id" => BSON::ObjectId('570c32f9370d935b78000001'), "name" => "Automobile" }, :description => "For northern living", :name => "Snowmobile", :price_in_cents => 345436 }

Going further• Indexing

• Map/Reduce to condense large volumes of data; work around for JOINS - rearrange data into aggregate forms

• Sharding

• Track data versioning

• GeoSpatial indexing

Worth it?• It depends what you want

• Have MongoDB serve up user info or cache preferences, integrate with social networks; SQL to handle payments/ordering

• Great for searching large amounts of data.

• PostgreSQL JSON type - support for arbitrary attributes (WARNING: anti-pattern)

• For this application, mongoid can work and clarified features for me, but SQL and Schema-based design seem optimized for this case

• Different ways to organize data and design models

• Learned some new things about ActiveRecord and PostgreSQL

Sourceshttps://www.mongodb.com/nosql-explained

Rege, Gautam. Ruby and MongoDB Web Development Beginner’s Guide, Packt Publishing, 2012

http://www.sarahmei.com/blog/2013/11/11/why-you-should-never-use-mongodb/ comment-page-1/

https://www.mongodb.com/compare/mongodb-mysql

https://github.com/meskyanichi/mongoid-paperclip

https://gorails.com/guides/setting-up-rails-4-with-mongodb-and-mongoid

https://gorails.com/blog/rails-4-0-with-mongodb-and-mongoid

http://stackoverflow.com/questions/26182890/perform-atomic-block-transactions-in-rails-with-mongoid

http://blog.2ndquadrant.com/postgresql-anti-patterns-unnecessary-jsonhstore-dynamic-columns/

@MrNickAltobelli nmhalt@gmail.com github: DataNick

www.nickaltobelli.com

Thank You!