Lecture 3 ActiveRecord Rails persistence layer



Similar documents
How To Create A Model In Ruby (Orm)

Topics. Query Repo Model Changeset

Ruby on Rails is a web application framework written in Ruby, a dynamically typed programming language The amazing productivity claims of Rails is

Building Dynamic Web 2.0 Websites with Ruby on Rails

Building and Deploying Web Scale Social Networking Applications Using Ruby on Rails and Oracle. Kuassi Mensah Group Product Manager

Object Oriented Databases. OOAD Fall 2012 Arjun Gopalakrishna Bhavya Udayashankar

Relational Databases. Christopher Simpkins

This tutorial has been designed for beginners who would like to use the Ruby framework for developing database-backed web applications.

Comparing Dynamic and Static Language Approaches to Web Frameworks

SQL Injection. SQL Injection. CSCI 4971 Secure Software Principles. Rensselaer Polytechnic Institute. Spring

Deep in the CRUD LEVEL 1

Scaling Rails with memcached

Apache Thrift and Ruby

Enterprise Recipes with Ruby and Rails

Rake Task Management Essentials

CS169.1x Lecture 5: SaaS Architecture and Introduction to Rails " Fall 2012"

Ruby On Rails. CSCI 5449 Submitted by: Bhaskar Vaish

PIC 10A. Lecture 7: Graphics II and intro to the if statement

What is a database? COSC 304 Introduction to Database Systems. Database Introduction. Example Problem. Databases in the Real-World

Lag Sucks! R.J. Lorimer Director of Platform Engineering Electrotank, Inc. Robert Greene Vice President of Technology Versant Corporation

Table of contents. Reverse-engineers a database to Grails domain classes.

Review Entity-Relationship Diagrams and the Relational Model. Data Models. Review. Why Study the Relational Model? Steps in Database Design

Gleb Arshinov, Alexander Dymo PGCon 2010 PostgreSQL as a secret weapon for high-performance Ruby on Rails applications

Polyglot Multi-Paradigm. Modeling. MDA in the Real World. Stefan Tilkov

Customer Bank Account Management System Technical Specification Document

Web Framework Performance Examples from Django and Rails

A Brief Introduction to MySQL

Zend Framework Database Access

COURSE NAME: Database Management. TOPIC: Database Design LECTURE 3. The Database System Life Cycle (DBLC) The database life cycle contains six phases;

Contents. 2 Alfresco API Version 1.0

1. What is SQL Injection?

Ruby on Rails Tutorial - Bounded Verification of Data Models

Brakeman and Jenkins: The Duo Detects Defects in Ruby on Rails Code

Ruby on Rails Secure Coding Recommendations

Database Query 1: SQL Basics

SQL Injection Attack Lab Using Collabtive

database abstraction layer database abstraction layers in PHP Lukas Smith BackendMedia

The Relational Model. Why Study the Relational Model? Relational Database: Definitions

Content Management System (Dokument- og Sagsstyringssystem)

A Beginner's Guide to Security with Ruby on Rails in BSD. Corey Benninger

The Relational Model. Why Study the Relational Model? Relational Database: Definitions. Chapter 3

Static Typing for Ruby on Rails

Ruby on Rails. Computerlabor

An Integrated Data Model Verifier with Property Templates

PostgreSQL Functions By Example

Using the SQL Server Linked Server Capability

CSCC09F Programming on the Web. Mongo DB

Ruby on Rails. Object Oriented Analysis & Design CSCI-5448 University of Colorado, Boulder. -Dheeraj Potlapally

Automating SQL Injection Exploits

Ruby On Rails. James Reynolds

Submit: An Online Submission Platform for Computer Science Courses

SQL Simple Queries. Chapter 3.1 V3.0. Napier University Dr Gordon Russell

Introduction to Python

Facebook Twitter YouTube Google Plus Website

Perl & NoSQL Focus on MongoDB. Jean-Marie Gouarné jmgdoc@cpan.org

Web Development using PHP (WD_PHP) Duration 1.5 months

A basic create statement for a simple student table would look like the following.

SQL Injection January 23, 2013

DIPLOMA IN WEBDEVELOPMENT

MapReduce. MapReduce and SQL Injections. CS 3200 Final Lecture. Introduction. MapReduce. Programming Model. Example

Webapps Vulnerability Report

Spark ΕΡΓΑΣΤΗΡΙΟ 10. Prepared by George Nikolaides 4/19/2015 1

SQL Injection Vulnerabilities in Desktop Applications

Full Text Search with Sphinx

The NetBeans TM Ruby IDE: You Thought Rails Development Was Fun Before

Scaling up = getting a better machine. Scaling out = use another server and add it to your cluster.

extensible record stores document stores key-value stores Rick Cattel s clustering from Scalable SQL and NoSQL Data Stores SIGMOD Record, 2010

Programming Language Rankings. Lecture 15: Type Inference, polymorphism & Type Classes. Top Combined. Tiobe Index. CSC 131! Fall, 2014!

BM482E Introduction to Computer Security

Authoring for System Center 2012 Operations Manager

Guides.rubyonrails.org

SERVICE-ORIENTED DESIGN WITH RUBY AND RAILS

Beginning ASP.NET 4.5

SQL - QUICK GUIDE. Allows users to access data in relational database management systems.

SQL: Programming. Introduction to Databases CompSci 316 Fall 2014

Writing Scripts with PHP s PEAR DB Module

Data Modeling. Database Systems: The Complete Book Ch ,

SnapLogic Salesforce Snap Reference

Rails Cookbook. Rob Orsini. O'REILLY 8 Beijing Cambridge Farnham Koln Paris Sebastopol Taipei Tokyo

Databases and BigData

1. Building Testing Environment

L7_L10. MongoDB. Big Data and Analytics by Seema Acharya and Subhashini Chellappan Copyright 2015, WILEY INDIA PVT. LTD.

Embedded SQL. Unit 5.1. Dr Gordon Russell, Napier University

Evaluation. Chapter 1: An Overview Of Ruby Rails. Copy. 6) Static Pages Within a Rails Application

Package uptimerobot. October 22, 2015

Real SQL Programming. Persistent Stored Modules (PSM) PL/SQL Embedded SQL

Resco CRM Server Guide. How to integrate Resco CRM with other back-end systems using web services

Pete Helgren Ruby On Rails on i

Web Application Security

Transcription:

Lecture 3 ActiveRecord Rails persistence layer Elektroniczne Przetwarzanie Informacji 9 maja 2013

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

Class and migration class Book < ActiveRecord::Base class CreateBooks < ActiveRecord::Migration def change create_table :books do t t.string :title t.references :author # same as # t.integer :author_id t.timestamps

CRUD create book = Book.new(:title => 'Ruby') book.save # or Book.create(:title => 'Ruby') # In the controller context if Book.create(params[:book]) # success else # failure # params[:book] looks like { :title => 'Ruby' }

CRUD read # find by id book = Book.find(1) book.title #=> 'Ruby' # or book = Book.find_by_id(1) # find by title book = Book.find_by_title('Ruby') # or book = Book.where(:title => 'Ruby')

CRUD update book = Book.find(1) book.title = 'Ruby for beginners' book.save # or Book.update_attributes(:title => 'Ruby for beginners') # In the controller context if Book.update_attributes(params[:book]) # success else # failure # params[:book] looks like { :title => 'Ruby for beginners' }

CRUD destroy book = Book.find(1) book.destroy book.title = 'Ruby for beginners' #=> error Book.find(1) #=> error Book.find_by_id(1) #=> nil

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

Primary key (id) # find by primary key: Book.find(1) # => book1 # as above (safe version): Book.find_by_id(1) # => book1 # find many by primary key: Book.find(1,3,4) # => [ book1, book3, book4 ] # first record Book.first # => book1 # last record Book.last # => book4 Aleksander # all Smywiński-Pohl records Lecture Book.all 3: ActiveRecord id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010

Find by # Find ALL Book.find_all_by_title('Ruby') # => [ book1, book2, book4 ] Book.find_all_by_title('Perl') # => [] # Find ONE Book.find_by_title('Ruby') # => book1 Book.find_by_title('Perl') # => nil id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010 # Find with two attributes Book.find_by_title_and_year('Ruby',2009) # => book2

Query language where defines the query condition(s) order defines the query order select defines the fields that will be returned (all by default) limit defines the maximal number of results offset defines the results offset includes eagerly loads associations group groups results by given attribute having defines the query condition(s) in case the results are grouped

where Book.where("title = #{params[:title]}") # - doesn't perform a query (is lazy evaluated) # - is a security risk - SQL-injection! Book.where("title =?", params[:title]) # - is automatically escaped - no SQL-injection Book.where("title =? AND year =?", 'Ruby', 2009).all # [book2] Book.where("title = :title", :title => 'Ruby').first # book1 id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010 Book.where(:title => 'Ruby').first # book1 Book.where(:year => (2009..2010)).all # [book2, book4]

order Book.order("year").all # [ book1, book2, book3, book4 ] Book.order("id DESC").all # [ book4, book3, book2, book1 ] Book.order("id").reverse_order.all # [ book4, book3, book2, book1 ] Book.order("title DESC, year ASC").first # book1 id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010

select book = Book.select('title').first book.title # => 'Ruby' book.year # ActiveModel::MissingAttributeError: # missing attribute: 'year' book.title = 'Ruby for beginners' book.save # ActiveRecord::ReadOnlyRecord books = Book.select('title').uniq books.size # => 2 books[0].title # 'Ruby' books[1].title # 'Python' id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010

limit, offset Book.limit(2).all # [ book1, book2 ] Book.offset(1).all # [ book2, book3, book4 ] Book.offset(0).all # [ book1, book2, book3, book4 ] Book.limit(2).offset(1).all # [ book2, book3 ] id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010 Book.limit(2).offset(1).reverse_order.all # [ book3, book2 ]

Pagination gem kaminari Book.page(2) # fetch the second page Book.page(2).per(50) # fetch the second page of 50 items (25 by default) gem will_paginate Book.paginate(:page => 2) # or Book.page(2) # fetch the second page Book.paginate(:page => 2, :per_page => 50) # fetch the second page of 50 items (30 by default)

includes n+1 query problem assuming there is an association between two classes, if we query for a collection of n objects, we will have to issue n queries for the associated objects (thus n + 1 queries will be issued). This is very slow. We can resolve this problem, using includes.

includes Book.all.each do book book.author.name # This will require 5 sql queries: # select * from books # select * from authors where author_id = 1 # select * from authors where author_id = 2 #... Book.includes(:author).all.each do book book.author.name # This will require only 2 sql queries: # select * from books # select * from authors where id in (1, 2, 3) id title year author_id 1 Ruby 2008 1 2 Ruby 2009 2 3 Python 2009 1 4 Ruby 2010 3

group, having books = Book.select("title, count(*)").group("title").all books[0].title # => 'Ruby' books[0].count # => 3 books[1].title # => 'Python' books[1].count # => 1 Book.select("title, count(*)").group("title"). having("count(*) > 1").all # => 'Ruby', 3 Book.select("title, count(*)").group("title"). where("year > 2008").all # => 'Ruby', 2 # => 'Python', 1 id title year 1 Ruby 2008 2 Ruby 2009 3 Python 2009 4 Ruby 2010

Scoping Some finder methods are more popular than others. We can make them more readable by providing scopes for them: class Author scope :living, where(:death_date => nil) scope :polish, where(:nationality => "Polish") # now we can write Author.living.polish.order("last_name, first_name") # instead of Author.where(:death_date => nil). where(:nationality => "Polish").order("last_name, first_name")

Default scope The same way we can define the default scope: class Book default_scope where("year > 1980") # Issuing any query will attach this condition Book.all # in fact Book.where("year > 1980").all # To skip the scoping, we write Book.unscoped.all

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

Types of associations has_many belongs_to has_one has_and_belongs_to_many has_many :through belongs_to :polymorphic

Conceptual model vs. object model class Person < ActiveRecord::Base class Author < Person has_many :books class Book < ActiveRecord::Base belongs_to :author has_and_belongs_to_many :categories class Category < ActiveRecord::Base has_and_belongs_to_many :books

belnogs_to class Book < ActiveRecord::Base belongs_to :author book.author book.author=(author) book.author?(some_author) book.author.nil? book.build_author(...) book.create_author(...)

has_many class Author < Person has_many :books author.books author.books<<(book) author.books.delete(book) author.books=[book1,book2,...] author.book_ids author.book_ids=[id1,id2,...] author.books.clear author.books.empty? author.books.size author.books.find(...) author.books.exists?(...) author.books.build(...) author.books.create(...)

has_and_belongs_to_many class Category < ActiveRecord::Base has_and_belongs_to_many :books category.books category.books<<(book) category.books.delete(book) category.books=[book1,book2,...] category.book_ids category.book_ids=[id1,id2,...] category.books.clear category.books.empty? category.books.size category.books.find(...) category.books.exists?(...) category.books.build(...) category.books.create(...)

has_many :through class Developer < ActiveRecord::Base has_many :projects has_many :tasks, :through => :projects class Project < ActiveRecord::Base belongs_to :developer has_many :tasks class Task < ActiveRecord::Base belongs_to :project Note: through association is read-only.

has_many :polymorphic class Comment < ActiveRecord::Base belongs_to :item, :polymorphic => true class Post < ActiveRecord::Base has_many :comments, :as => :item class Image < ActiveRecord::Base has_many :comments, :as => :item post = Post.new post.comments << Comment.new image = Image.new image.comments << Comment.new

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

Predefined validations presence presence of an attribute acceptance acceptance of a condition confirmation confirmation (equality with confirmation field) of an attribute exclusion exclusion of the value of an attribute inclusion inclusion of the value of an attribute format match with a pattern length length (of text) numericality numericality (the feature of being a number) of an attribute uniqueness uniqueness of an attribute (better served on the DB level with appropriate field option) associated validation of an associated object

presence class Book < ActiveRecord::Base validate :title, :presence => true book = Book.new book.save # => false book.title = 'Ruby' book.save # => true

acceptance class Order < ActiveRecord::Base validate :terms_of_service, :acceptance => true order = Order.new order.save # => false order.terms_of_service = true order.save # => true

confirmation class Account < ActiveRecord::Base validate :password, :confirmation => true account = Account.new account.password = '123456' account.save # => false account.password_confirmation = '123' account.save # => false account.password_confirmation = '123456' account.save # => true

exclusion class Language < ActiveRecord::Base validate :name, :exclusion => { :in => ['PHP'] } language = Language.new(:name => 'PHP') language.save # => false language.name = 'Ruby' language.save # => true

inclusion class Person < ActiveRecord::Base validate :ger, :inclusion => { :in => %w{male female} } person = Person.new(:ger => "child") person.save # => false person.ger = 'female' person.save # => true

format class Book < ActiveRecord::Base validate :title, :format => { :with => /\A[A-Z](\w \s)+\z/ } book = Book.new(:title => "bad title") book.save # => false book.title = 'Ruby' book.save # => true

length class Person < ActiveRecord::Base validate :name, :length => { :in => 4..10 } person = Person.new(:name => "Jo") person.save # => false person.name = 'Jolomolodonton' person.save # => false person.name = 'Jolomolo' person.save # => true in minimum maximum is

numericality class Person < ActiveRecord::Base validate :age, :numericality => { :only_integer => true } validate :shoe_size, :numericality => true person = Person.new(:age => 10.1, :shoe_size => "a") person.save # => false person.age = 10 person.shoe_size = 5.5 person.save # => true

uniqueness class Person < ActiveRecord::Base validate :email person = Person.new(:email => "john@domain.com") person.save # => true person = Person.new(:email => "john@domain.com") person.save # => false scope

associated class Person < ActiveRecord::Base has_many :orders validates_associated :orders Calls valid? on each associated object.

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

Creating an object before_validation after_validation before_save around_save before_create around_create after_create after_save

Updating an object before_validation after_validation before_save around_save before_update around_update after_update after_save

Destroying an object before_destroy around_destroy after_destroy

Callback example class Post < ActiveRecord::Base validate :title, :format => {:with => /\A\w+\z/} before_validation { post post.title.strip! } # same as before_validation :strip_title protected def strip_title self.title.strip! Do not use callbacks to implement sophisticated business logic. Use service objects instead.

Service object class UserCreator def initialize(factory=user,social_network=socialnetwork, mailer=usermailer) @factory = factory @mailer = mailer @social_network = social_network def create(params) user = @factory.new(params) user.save @mailer.s_confirmation(user) @social_network.user_created(user) user user = UserCreator.new.create(params)

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

User input and SQL SQL-injection passing SQL commands as SQL query fragments to perform uninted queries attribute mass-assignment problem passing attribute values that are not expected by the developer

SQL-injection User.where("name = '#{params[:name]}'") params[:name] = "some name'; DROP TABLE users; --" "SELECT * from users where name = 'some name'; DROP TABLE users; --'" # This is a valid SQL query (two in fact). The code after -- is omitted. # Safe: User.where("name =?", params[:name]) User.where(:name => params[:name])

Mass assignment User.update_attributes(params[:user]) # Rails by default converts the passed key-value pairs into a hash. # An attacker can easily provide the following data: params[:user] = { :admin => true,... } # In older Rails we have to protect individual attributes # (black-list approach) class User < ActiveRecord::Base attr_protected :admin # In current Rails we have to specify all accessible attributes # (white-list approach): class User < ActiveRecord::Base attr_accessible :name, :surname

Mass assignment class AccessRulesController def grant_administrator_rights user = User.find(params[:id]) user.admin = true user.save In the next version of Rails the problem will be solved on the controller level with strong parameters.

Strong parameters class BooksController < ActionController::Base def create @book = Book.create(params[:book]) # raises exception def update @book = Book.find(params[:id]) @book.update_attributes(book_params) redirect_to @book private def book_params params.require(:book).permit(:title, :author_id)

ActiveRecord interface where, order, select,... there are many methods that are added to the class if it inherits from AR it is very hard to write isolated tests by stubbing these methods AR defines infinite protocol for the classes

How to overcome these problems do not call AR methods in the cooperating classes provide domain-specific methods which hide the AR calls separate unit tests that don t touch the database from integration tests that need access to the persistence layer when running the unit tests use NullDB use FigLeaf to make most of the AR methods private

NullDB # Avdi Grimm "Objects on Rails" module SpecHelpers def setup_nulldb schema_path = File.expand_path('../db/schema.rb', File.dirname( FILE )) NullDB.nullify(schema: schema_path) def teardown_nulldb NullDB.restore

FigLeaf # Avdi Grimm "Objects on Rails" class TodoList < ActiveRecord::Base include FigLeaf hide ActiveRecord::Base, ancestors: true, except: [Object, :init_with, :new_record?, :errors, :valid?, :save] hide_singletons ActiveRecord::Calculations, ActiveRecord::FinderMethods, ActiveRecord::Relation #... def self.with_name(name) where(:name => name) TodoList.where(:name => "Some name") # error: call to private method 'where' TodoList.with_name("Some name") # OK

Aga ActiveRecord basics Query language Associations Validations Callbacks ActiveRecord problems Alternatives

DataMapper variety of data-stores legacy database schemas composite primary keys auto-migrations support for DB-level data integrity strategic eager loading querying by associations identity map lazy properties object-oriented comparison of fields

Legacy DB support class Post include DataMapper::Resource # set the storage name for the :legacy repository storage_names[:legacy] = 'tblpost' # use the datastore's 'pid' field for the id property. property :id, Serial, :field => :pid # use a property called 'uid' as the child key (the foreign key) belongs_to :user, :child_key => [ :uid ]

Composite keys class LineItem include DataMapper::Resource property :order_id, Integer, :key => true property :item_number, Integer, :key => true order_id, item_number = 1, 1 LineItem.get(order_id, item_number) # => [#<LineItem @orderid=1 @item_number=1>]

Auto-migrate class Person include DataMapper::Resource property :id, Serial property :name, String, :required => true DataMapper.auto_migrate!

Auto-upgrade class Person include DataMapper::Resource property :id, Serial property :name, String, :required => true # new property property :hobby, String DataMapper.auto_upgrade!

Data integrity DB level class Person include DataMapper::Resource property :id, Serial has n, :tasks, :constraint => :destroy class Task include DataMapper::Resource property :id, Serial belongs_to :person

Lazy properties class Animal include DataMapper::Resource property :id, Serial property :name, String property :notes, Text # lazy-loads by default

Field comparison Zoo.first(:name => 'Galveston') # 'gt' means greater-than. 'lt' is less-than. Person.all(:age.gt => 30) # 'gte' means greather-than-or-equal-to. 'lte' is also available Person.all(:age.gte => 30) Person.all(:name.not => 'bob') # If the value of a pair is an Array, we do an IN-clause for you. Person.all(:name.like => 'S%', :id => [ 1, 2, 3, 4, 5 ]) # Does a NOT IN () clause for you. Person.all(:name.not => [ 'bob', 'rick', 'steve' ]) # Ordering Person.all(:order => [ :age.desc ]) #.asc is the default

Sequel concise DSL for constructing SQL queries and table schemas optimized for speed thread safety connection pooling prepared statements bound variables stored procedures savepoints two-phase commit transaction isolation master/slave configurations database sharding

Short example DB.create_table :items do primary_key :id String :name Float :price items = DB[:items] # Create a dataset # Populate the table items.insert(:name => 'abc', :price => rand * 100) items.insert(:name => 'def', :price => rand * 100) items.insert(:name => 'ghi', :price => rand * 100) # Print out the number of records puts "Item count: #{items.count}" # Print out the average price puts "The average price is: #{items.avg(:price)}"