Using Mongoid with Ruby on Rails
-
Upload
nicholas-altobelli -
Category
Technology
-
view
625 -
download
1
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/