Download presentation
Presentation is loading. Please wait.
1
Rails Programming today is a race between software engineers striving to build better and bigger idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning. - Rick Cook No, I'm not Rick
2
Inventory Application
3
So, I was reading my email Tuesday morning A fellow that reports to me came in to talk about how we order and receive pcs and printers And I've been wanting to convert our machine move form to a web page Suddenly, a real project…
4
Requirements A form that can handle both acquisitions and moves An email hook so we can create remedy tickets as part of the process A mechanism for marking records as processed, and for moving data into our main inventory database This should be simple, following KISS
5
What we'll cover Migrations and more about Sqlite3 Validations
6
The usual Create a rails framework Note backslashes Also, use of underscores Some of this is bad thinking… rails inventory cd inventory ruby script/generate scaffold Machine \ user_name:string \ user_name:string \ date_submitted:datetime \ date_submitted:datetime \ ticket_number:integer \ ticket_number:integer \ from_location:string \ from_location:string \ to_location:string \ to_location:string \ from_entity:string \ from_entity:string \ to_entity:string \ to_entity:string \ old_machine_name:string \ old_machine_name:string \ new_machine_name:string \ new_machine_name:string \ serial_number:string \ serial_number:string \ unc_number:string \ unc_number:string \ comments:text \ comments:text \ status:string \ status:string \ exported_to_main:boolean \ exported_to_main:boolean \ unneeded_field:decimal unneeded_field:decimal
7
Generation of the first Migration The generation of the scaffold creates: The view A controller A model Also a database migration file in the db directory, in this case: 20081104182035_create_machines.rb Note the timestamp and the conventional name
8
What does this file do? This file is a script, that contains a class, with two defined methods One method creates the database table creates initial fields sets types The other method undoes everything the first one does
9
class CreateMachines < ActiveRecord::Migration def self.up def self.up create_table :machines do |t| create_table :machines do |t| t.string :user_name t.string :user_name t.datetime :date_submitted t.datetime :date_submitted t.integer :ticket_number t.integer :ticket_number t.string :from_location t.string :from_location t.string :to_location t.string :to_location t.string :from_entity t.string :from_entity t.string :to_entity t.string :to_entity t.string :old_machine_name t.string :old_machine_name t.string :new_machine_name t.string :new_machine_name t.string :serial_number t.string :serial_number t.string :unc_number t.string :unc_number t.text :comments t.text :comments t.string :status t.string :status t.boolean :exported_to_main t.boolean :exported_to_main t.decimal :unneeded_field t.decimal :unneeded_field t.timestamps t.timestamps end end 1st Part Class inherits from ActiveRecord::Mi gration self.up is a method applied when a migration is run A loop assigns type and names
10
def self.down def self.down drop_table :machines drop_table :machines end endend 2nd Part a second method provides a way to roll back the migration Done properly, this allows one to move forward and back in database "versions" without affecting other structures
11
Migrations You can modify this file before applying it, adding additional options such as field lengths, default values, etc
12
What's the point? Migrations allow manipulation of the database with some version control You could also manually manipulate the database, but you'd have to keep track of the changes But some migrations are irreversible, and if you don't define a good way back…. To protect against that, backup! Or use version control systems like cvs, subversion, git
13
schema.rb Stored in db/ This is the canonical representation of the current state of the database You could modify this--don't Generated after each migration You can use this with db:schema:load to implement the same db structures on another system
14
ActiveRecord::Schema.define :version => 20081105005808 do create_table "machines", :force => true do |t| create_table "machines", :force => true do |t| t.string "user_name" t.string "user_name" t.datetime "date_submitted" t.datetime "date_submitted" t.integer "ticket_number" t.integer "ticket_number" t.string "from_location" t.string "from_location" t.string "to_location" t.string "to_location" t.string "from_entity" t.string "from_entity" t.string "to_entity" t.string "to_entity" t.string "old_machine_name" t.string "old_machine_name" t.string "new_machine_name" t.string "new_machine_name" t.string "serial_number" t.string "serial_number" t.integer "unc_number", :limit => 255 t.integer "unc_number", :limit => 255 t.text "comments" t.text "comments" t.string "status" t.string "status" t.boolean "exported_to_main" t.boolean "exported_to_main" t.datetime "created_at" t.datetime "created_at" t.datetime "updated_at" t.datetime "updated_at" end end
15
But… We haven't run our first migration yet rake db:migrate This command applies all unapplied migrations in the db/migrate dir The timestamps for the migrations are stored in a table in the database, schema_migrations (this is how rails keeps track of migrations)
16
What rake really does Analogous to make, it looks for a file to process in order to build something rake db:migrate looks in the db/migrate folder, and finds any of the migration files that have not yet been applied, and runs those Each time you want to make changes to the db, you generate a migration script, edit that, then use rake to migrate the changes into the db
17
About the database By default, rails 2.1 uses sqlite3, other dbs are also possible to use, like mysql sqlite3 databases are single files, eg. development.sqlite3 We can look at the database directly look, but don't touch!
18
Sqlite3 syntax Commands that manipulate the db begin with a period Sql commands don’t and must end with a semicolon Get help with.help, exit with.exit
19
Some sqlite3 commands.databases List names and files of attached databases.exit Exit this program.header s ON|OFF Turn display of headers on or off.help Show this message.output FILENAME Send output to FILENAME.output stdout Send output to the screen.quit Exit this program.read FILENAME Execute SQL in FILENAME.schema ?TABLE? Show the CREATE statements.separator STRING Change separator used by output mode and.import.show Show the current values for various settings
20
A Sample Session sqlite>.tables machines schema_migrations sqlite>.schema machines CREATE TABLE "machines" "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_name" varchar 255, "date_submitted" datetime, "ticket_number" integer, "from_location" varchar 255, "to_location" varchar 255, "from_entity" varchar 255, "to_entity" varchar 255, "old_machine_name" varchar 255, "new_machine_name" varchar 255, "serial_number" varchar 255, "unc_number" varchar 255, "comments" text, "status" varchar 255, "exported_to_main" boolean, "unneeded_field" decimal, "created_at" datetime, "updated_at" datetime ; sqlite>.exit
21
You might have noticed There's a field named unneeded_field I don't need this field, so we'll look at dumping it To do this, create a new migration hays$ script/generate migration \ RemoveColumn_uneeded_field exists db/migrate create db/migrate/20081104181336_remove_column_uneeded_field.rb
22
A blank migration Now we have a blank migration file: 20081104181336_remove_column_une eded_field.rb file in db/migrate Yes the name is long, but it's explicit and helps us know what the migration was supposed to do We have to edit this file with the commands we need to apply to the database (rails, as good as it is, cannot read minds)
23
A blank migration Again, a class with two methods, but nothing in them class RemoveColumnUneededField < \ ActiveRecord::Migration def self.up def self.up end end def self.down def self.down end endend
24
Filling the empty migration We'll use remove_column with the table and field name, and add_column to undo the change in case we were wrong class RemoveColumnUneededField < \ ActiveRecord::Migration ActiveRecord::Migration def self.up def self.up remove_column :machines, :unneeded_field remove_column :machines, :unneeded_field end end def self.down def self.down add_column :machines, :unneeded_field, :decimal add_column :machines, :unneeded_field, :decimal end endend
25
create_table name, options drop_table name rename_table old_name, new_name add_column table_name, column_name, type, options rename_column table_name, column_name, new_column_name change_column table_name, column_name, type, options remove_column table_name, column_name add_index table_name, column_names, options remove_index table_name, index_name from http://api.rubyonrails.org/classes/ActiveRecord/Migration.html Available migration commands These are the current commands you can use
26
Up and down Use rake db:migrate to apply this ne migration (the assumption is that we want to apply a new migration) Use rake db:migrate:down VERSION=xxxxxxxxxxxxxx where xxxxxxxxxxxxxx is the timestamp of the migration file. So if we run rake db:migrate:down VERSION=20081104182506, we get the column back
27
Running the Migration When you run it, if you don’t get an error, you'll see something like this hays$ rake db:migrate (in /Volumes/BIL/INLS672/samples/ruby/inventory) == 20081104182506 RemoveColumnUneededField: migrating =========== -- remove_column(:machines, :unneeded_field) -> 0.3480s -> 0.3480s == 20081104182506 RemoveColumnUneededField: migrated (0.3494s) ===
28
Results hays$ sqlite3 db/development.sqlite3 SQLite version 3.4.0 Enter ".help" for instructions sqlite>.schema machines CREATE TABLE "machines" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "user_name" varchar(255), AUTOINCREMENT NOT NULL, "user_name" varchar(255), "date_submitted" datetime, "ticket_number" integer, "from_location" "date_submitted" datetime, "ticket_number" integer, "from_location" varchar(255), "to_location" varchar(255), "from_entity" varchar(255), varchar(255), "to_location" varchar(255), "from_entity" varchar(255), "to_entity" varchar(255), "old_machine_name" varchar(255), "to_entity" varchar(255), "old_machine_name" varchar(255), "new_machine_name" varchar(255), "serial_number" varchar(255), "new_machine_name" varchar(255), "serial_number" varchar(255), "unc_number" varchar(255), "comments" text, "status" varchar(255), "unc_number" varchar(255), "comments" text, "status" varchar(255), "exported_to_main" boolean, "created_at" datetime, "updated_at" "exported_to_main" boolean, "created_at" datetime, "updated_at" datetime); datetime); sqlite>.exit
29
Rolling back We can get the column back by running: rake db:migrate:down VERSION=20081104182506 But we can't get the data that was in the column back
30
An aside Rail documentation Tutorials for 2.1 are generally just how to get started API doc are the most complete, but not very readable--see http://api.rubyonrails.org/ http://api.rubyonrails.org/ Lots of code snippets out there, playing with those are a good was to learn new things--most of these are unixy and terse Agile Web Development with Rails is the best book I've found. see: http://www.pragprog.com/http://www.pragprog.com/ Practical Rails Projects, see http://www.apress.com/book/view/9781590597811 http://www.apress.com/book/view/9781590597811
31
An aside Go with the flow Rails is complicated enough that it's best to roll with it This is partly due to the emphasis on convention over configuration The conundrum is where does the knowledge lie CLI versus GUI DB versus Middleware versus Browser Thick versus Thin clients
32
After all this… We've looked at migrations and the database Migrations do not affect other parts of the applications, such as the model, controller(s), or any of the views We dropped a column after the scaffolding, so the views reference unneeded_field So we get an error when we try to run the pages…
33
The error
34
Easy peasy The error message references a method, this is one way fields in the db are accessed Also shows us source code around the offense
35
Cleaning up In each of the views we need to remove the references For example, in new.html.erb and edit.html.erb:
36
Now it works
37
Validations
38
Simple validation Now that we have the db straighten out (yeah, right), time to add some validations These belong in the model, machine.rb in app/models Right now, that's just: class Machine < ActiveRecord::Base end
39
Included validations Our class has access to classes and methods from ActiveRecord::Validations The simplest is validates_presence_of Usage: # Validate that required fields are not empty validates_presence_of :user_name, \ :date_submitted, :from_location, \ :from_entity, :to_location, :to_entity, :status see http://api.rubyonrails.org/classes/ActiveRecord/Validations.html
40
Other Included Validations validates_acceptance_of validates_associated validates_confirmation_of validates_each validates_exclusion_of validates_format_of validates_inclusion_of validates_length_of validates_numericality_of validates_presence_of validates_size_of validates_uniqueness_of from http://api.rubyonrails.org/classes/ActiveRecord/Validations/ClassMethods.html
41
Validations added to the model These went easily: #Validates to_location, that should only 6 chars, we'll allow 10 validates_length_of :to_location, :maximum=>15 #Validates fields that should not be more than 15 characters validates_length_of :user_name, :maximum=>15 #Validates fields that should not be more than 30 chars validates_length_of :from_location, :maximum=>30 validates_length_of :from_entity, :maximum=>30 validates_length_of :to_entity, :maximum=>30 # None of these affect the database field lengths
42
A rabbit hole And, also, I want to make sure that unc_number is an integer, but it can be empty, so I try this: #Validates that unc number is a number validates_numericality_of :unc_number,\ :allow_nil=>true, :only_integer=>true, But it fail--if the field is empty it throws an error…
43
After digging Google is my friend, and I find: http://opensoul.org/2007/2/7/validations- on-empty-not-nil-attributes http://opensoul.org/2007/2/7/validations- on-empty-not-nil-attributes This suggests that the problem is that unc_number is really a string, and that an empty string is empty, not nil…
44
But where? HTML knows shinola about text/integer types But no errors on stuffing the wrong kind of data into a field (esp no ugly ones), so likely sqlite3 doesn't really care But the db type is involved since in the migration it was defined with: t.string :unc_number So rails knows it's a string
45
A hack Brandon's approach is to define a method that goes through all of the params passed to the model for validation and if empty, set to nil…. def clear_empty_attrs def clear_empty_attrs @attributes.each do |key,value| @attributes.each do |key,value| self[key] = nil if value.blank? self[key] = nil if value.blank? end end
46
A hack This method must be called before the validation are run, so it goes to the top of the model (not required, JAGI) This is done using before_validation So before the validation sequence, all empties are converted to nil Does this seem like a good fix? before_validation :clear_empty_attrs
47
A hack One concern I had was this it hitting all of the fields--and that way leads to madness--so I limited it protected def clear_empty_attrs # we don't need to loop through everything, so I'm # just calling the one field # @attributes.each do |key,value| #self[key] = nil if value.blank? self[:unc_number] = nil if unc_number.blank? # end end
48
Works, but…. It is a hack Using it, I'm working around an error that's due to a bad choice I made-- rarely a good idea, and these things may take time to bite I'm also going against the flow So what to do to fix it? Test, research, change, rinse and repeat
49
Back to the beginning As it turns out, I did define an integer in the database, ticket_number ruby script/generate scaffold Machine \ user_name:string \ date_submitted:datetime \ ticket_number:integer \ from_location:string \ to_location:string \ from_entity:string \ to_entity:string \ old_machine_name:string \ new_machine_name:string \ serial_number:string \ unc_number:string \ comments:text \ status:string \ exported_to_main:boolean \ unneeded_field:decimal
50
An easy test I try the same validation against that field and it works, so I confirm the problem is the field type Note the error message… validates_numericality_of\ :ticket_number,\ :ticket_number,\ :only_integer=>true,\ :only_integer=>true,\ :allow_nil=>true,\ :allow_nil=>true,\ :message=>'must be an integer if not blank' :message=>'must be an integer if not blank'
51
A new migration So, I need to change the type of unc_number Again leaving a way back…. This fixed it for real class ChangeTextToDecimalsUncNumber < ActiveRecord::Migration < ActiveRecord::Migration def self.up def self.up change_column(:machines, :unc_number, :integer) change_column(:machines, :unc_number, :integer) end end def self.down def self.down change_column(:machines, :unc_number, :string) change_column(:machines, :unc_number, :string) end endend
52
Custom Validations It's also easy to write custom validations Define a method in the protected section (since we don't need this outside our class) Call that method as a symbol in the validation section: validate :our_method As an example, we'll work with ticket_number, even tho it's an artificial example
53
A new method First, we'll check the value and make sure it's above 1000 In testing this works ok, but obviously it won't accept a nil value def ticket_number_must_be_greater_than_1000 errors.add(:ticket_number, 'must be greater than 1000')\ errors.add(:ticket_number, 'must be greater than 1000')\ if ticket_number < 1001 if ticket_number < 1001 end end
54
Not a nil So we need to check for not nil and less than 1001 Use a bang (!) to say not def ticket_number_must_be_greater_than_1000 errors.add(:ticket_number, 'must be greater than 1000')\ errors.add(:ticket_number, 'must be greater than 1000')\ if !ticket_number.nil? \ if !ticket_number.nil? \ && ticket_number < 1001 && ticket_number < 1001end
55
Time Validations We want the date_submitted to be reasonable Fortunately, rails understands time and dates
56
Time Methods ago day days fortnight fortnights from_now hour hours minute minutes month months second seconds since until week weeks year years
57
Another Validation # This validates that the date and time are resonable values def date_submitted_must_be_sensible def date_submitted_must_be_sensible errors.add(:date_submitted, \ errors.add(:date_submitted, \ 'Time and date cannot be in the past')\ 'Time and date cannot be in the past')\ if date_submitted < Time.now if date_submitted < Time.now errors.add(:date_submitted, \ errors.add(:date_submitted, \ 'Time and date cannot be more than 2 years in the future')\ 'Time and date cannot be more than 2 years in the future')\ if date_submitted > Time.now.advance(:years => 2) if date_submitted > Time.now.advance(:years => 2) # is equivalent to: # is equivalent to: #if date_submitted > 2.years.from_now #if date_submitted > 2.years.from_now end end
58
Other minor changes Stripped down the index view, don't need that much detail This version is tarred and gzipped in the samples dir as inventory01.gz.tar
59
Intelligence in the middleware Although sqlite3 types this field as a string, it doesn't care what the contents are Rails does care though So most all of the control mechanisms are in rails This is a common approach Makes management of the system easier
60
Sources http://dizzy.co.uk/ruby_on_rails/cheatsh eets/rails-migrations http://dizzy.co.uk/ruby_on_rails/cheatsh eets/rails-migrations http://opensoul.org/2007/2/7/validations- on-empty-not-nil-attributes http://opensoul.org/2007/2/7/validations- on-empty-not-nil-attributes http://rubyonrailsprogrammingtips.com/ http://linuxgazette.net/109/chirico1.html
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.