Presentation is loading. Please wait.

Presentation is loading. Please wait.

Rails Programming today is a race between software engineers striving to build better and bigger idiot-proof programs, and the Universe trying to produce.

Similar presentations


Presentation on theme: "Rails Programming today is a race between software engineers striving to build better and bigger idiot-proof programs, and the Universe trying to produce."— Presentation transcript:

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


Download ppt "Rails Programming today is a race between software engineers striving to build better and bigger idiot-proof programs, and the Universe trying to produce."

Similar presentations


Ads by Google