1 / 60

Rails

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. Inventory Application. So, I was reading my email.

leal
Download Presentation

Rails

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

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 rails inventory cd inventory 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 • Create a rails framework • Note backslashes • Also, use of underscores • Some of this is bad thinking…

  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. 1st Part class CreateMachines < ActiveRecord::Migration def self.up create_table :machines do |t| t.string :user_name t.datetime :date_submitted t.integer :ticket_number t.string :from_location t.string :to_location t.string :from_entity t.string :to_entity t.string :old_machine_name t.string :new_machine_name t.string :serial_number t.string :unc_number t.text :comments t.string :status t.boolean :exported_to_main t.decimal :unneeded_field t.timestamps end end • Class inherits from ActiveRecord::Migration • self.up is a method applied when a migration is run • A loop assigns type and names

  10. 2nd Part def self.down drop_table :machines end end • 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| t.string "user_name" t.datetime "date_submitted" t.integer "ticket_number" t.string "from_location" t.string "to_location" t.string "from_entity" t.string "to_entity" t.string "old_machine_name" t.string "new_machine_name" t.string "serial_number" t.integer "unc_number", :limit => 255 t.text "comments" t.string "status" t.boolean "exported_to_main" t.datetime "created_at" t.datetime "updated_at" 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_uneeded_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 end def self.down end end

  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 def self.up remove_column :machines, :unneeded_field end def self.down add_column :machines, :unneeded_field, :decimal end end

  25. Available migration commands • These are the current commands you can use 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

  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 == 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), "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, "created_at" datetime, "updated_at" 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--seehttp://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/ • Practical Rails Projects, see 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:<p><%= f.label :unneeded_field %><br /><%= f.text_field :unneeded_field %> </p>

  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::Baseend

  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 emptyvalidates_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 numbervalidates_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 • 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 @attributes.each do |key,value| 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 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 • As it turns out, I did define an integer in the database, ticket_number

  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,\ :only_integer=>true,\ :allow_nil=>true,\ :message=>'must be an integer if not blank'

More Related