1 / 60

Son of the Inventory App

Son of the Inventory App. But first. The question came up about how to run a production server with Rails…. Three Possibilities. Use fastcgi and apache Use apache as a proxy server, against a cluster of mongrel servers (this is covered in Agile Web Development with Rails)

doane
Download Presentation

Son of the Inventory App

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. Son of the Inventory App

  2. But first • The question came up about how to run a production server with Rails…

  3. Three Possibilities • Use fastcgi and apache • Use apache as a proxy server, against a cluster of mongrel servers (this is covered in Agile Web Development with Rails) • And the one you want, passenger:http://www.modrails.com/ • I'm still playing with this, more to come…

  4. Installation • It's a gem so use "sudo gem install passenger" • Then run:sudo passenger-install-apache2-module • This will install the modules needed

  5. Migrate to production • rake migrate RAILS_ENV=production • This will create a new empty database for a production server • You can copy your development db over the production one if you want to carry your test data over, or dump and load the data

  6. Configuration • In the apache config file, check to make sure you have the modules loading • I had to add these manually on my laptop LoadModule passenger_module /usr/local/lib/ruby/gems/1.8/ gems/passenger-2.0.3/ext/apache2/mod_passenger.so # Passenger config PassengerRoot /usr/local/lib/ruby/gems/1.8/gems/passenger-2.0.3 PassengerRuby /usr/local/bin/ruby

  7. Virtual Host • You need to define a virtual host • This is what I’m using for now • Note that I’m using podzone.net, this is dyndns • The base uri should point to a soft link to your application's public folder--doesn't work for me <VirtualHost *:80> ServerAdmin webmaster@dummy-host2.example.com DocumentRoot /Library/WebServer/Documents RailsBaseURI /inventory RailsSpawnMethod conservative ServerName avatar.podzone.net </VirtualHost>

  8. Odds and Ends • After configuring the web server, restart it:sudo apachectl graceful • Watch your logs:tail -f /var/log/httpd/error_log

  9. Tonight and Next Week • Tonight • time and date • tags • lots searches • Next Week • more searches • email • filesystem access • ?

  10. When we left our application • A database backend • Some Migrations • Played with Types • Played with Time Methods • Some Validations

  11. Let's take a look at the source • Model • Controller • Views

  12. Next Steps • Refine status options • Refine date_submitted • Add searches

  13. Status • We can't depend on the user to type what we want • So we need to define what statuses we'll accept • ordered, delivered, deployed • Provide a controlled method for selection • Radio buttons

  14. new.html.erb • In the views/machine dir, we need to edit new.html.erb file • This one's easy, note we're setting a default <p> <%= f.label :status %><br /> <%= f.radio_button :status, "ordered", :checked => true %> Ordered <br/> <%= f.radio_button :status, "delivered" %>Delivered <br/> <%= f.radio_button :status, "deployed" %>Deployed <br/> </p>

  15. edit.html.erb • For the edit view, the same thing without the default <p> <%= f.label :status %><br /> <%= f.radio_button :status, "ordered" %>Ordered <br/> <%= f.radio_button :status, "delivered" %>Delivered <br/> <%= f.radio_button :status, "deployed" %>Deployed <br/> </p>

  16. A validation • We might not need it, but jic • Note the use of %w Check status for a valid choice validates_inclusion_of \ :status, \ :in => %w(ordered delivered deployed), \ :message => "Status must be ordered, delivered, or deployed"

  17. date_submitted date_select datetime_select distance_of_time_in_words distance_of_time_in_words_to_now select_date select_datetime select_day select_hour select_minute select_month select_second select_time select_year time_ago_in_words time_select • The date_submitted is just too precise--we don't need the time values, just the date • We're using datetime_select, but time and date methods?

  18. What's the difference? • date_select, datetime_select, select_time generate inputs tied to an object in the model • select_date, select_datetime, select_day, select_hour, select_minute, select_month, select_second, select_time, select_year produce inputs, but these are not tied to an object in the model • distance_of_time_in_words, distance_of_time_in_words_from_now, time_ago_in_words calculate intervals • This distinction between methods tied to the model and those which are not is pervasive

  19. date_select • date_select give us an input for just the date • Lots of options <p> <%= f.label :date_submitted %><br /> <%= f.date_select \ :date_submitted, \ :order => [:month, :day, :year], \ :start_year => 2008 %> </p>

  20. Another Validation • We should also refine the date_submitted validation • We don't allow selection in the past, but we should allow that for at least a week or so in the past • We should limit the future as well

  21. First Try: 1 day ago and Err This validates that the date and time are reasonable values def date_submitted_must_be_sensible errors.add(:date_submitted, \ 'Time and date cannot be in the past')\ if date_submitted < 3600.mins.ago This one is too restrictive if date_submitted < Time.now errors.add(:date_submitted, \ 'Time and date cannot be more than 2 years in the future')\ if date_submitted > Time.now.advance(:years => 2) is equivalent to: #if date_submitted > 2.years.from_now end

  22. This Works # This validates that the date and time are resonable values def date_submitted_must_be_sensible errors.add(:date_submitted, \ 'Time and date cannot be more than 2 months in the past')\ if date_submitted < 2.months.ago # This one is too restrictive # if date_submitted < Time.now errors.add(:date_submitted, \ 'Time and date cannot be more than 2 years in the future')\ if date_submitted > Time.now.advance(:years => 2) # is equivalent to: #if date_submitted > 2.years.from_now end

  23. Refining Date Appearance • The inputs are good, but the display leaves a bit to be desired…

  24. Using strftime • strftime is a method that will format a datetime object in custom format • The way it works is you call the method with a string that represents the format you want • The % sigil represents a time or date symbol <p> <b>Date submitted:</b> <%=h @machine.date_submitted.strftime "%m-%d-%Y" %> </p>

  25. strftime formatting %a - abbreviated weekday name according to the current locale %A - full weekday name according to the current locale %b - abbreviated month name according to the current locale %B - full month name according to the current locale %c - preferred date and time representation for the current locale %C - century number (the year divided by 100 and truncated to an integer) %d - day of the month as a decimal number (range 01 to 31) %D - same as %m/%d/%y %e - day of the month as a decimal number %g - like %G, but without the century. %H - hour as a decimal number using a 24-hour clock (range 00 to 23) %I - hour as a decimal number using a 12-hour clock (range 01 to 12) %j - day of the year as a decimal number (range 001 to 366) %m - month as a decimal number (range 01 to 12) %M - minute as a decimal number %n - newline character %p - either `am' or `pm' according to the given time value %r - time in a.m. and p.m. notation %R - time in 24 hour notation %S - second as a decimal number %t - tab character %T - current time, equal to %H:%M:%S %u - weekday as a decimal number [1,7], with 1 representing Monday • Lots of options • There are a few more • I think all of these work with rails from http://us.php.net/strftime

  26. Refining our index • Currently, the index.html.erb file returns all of the records, by virtue of @machines = Machine.find(:all) • We don't really want that, since any records exported to the main db are not longer relevant @machines = Machine.find(\ :all, \ :conditions => { :exported_to_main => false})

  27. Searches • At this point, we have a pretty nice app • But we don't have a search tool • We'll want a simple search for the main page • Also an advanced search page with more options

  28. Simple Search • For this we'll use a find method • We'll need an if statement in the controller • We'll also need an input on the index.html.erb file • To get started, we'll just search for a name match

  29. The input • So far, the inputs we have are all associated with objects in the model • We don't need a db entry tho • Use an input not linked to the model, text_field_tag • Inputs independent of the model end in _tag by convention

  30. The Input • Here's the input • Note the use of the params hash • Also that the name of the submit_tag is nil • This will default to a get <p> <%= text_field_tag :general_search, params[:search] %> <%= submit_tag "General Search", :name => nil %> </p>

  31. An if to catch the search • We need to check if we have the get var • Then we set @machines to be the result of a find • Here, the find just looks for a match between the data passed via the get into the params hash and the user_name object in the model… if params[:general_search] @machines = Machine.find(\ :all, \ :conditions => [ "user_name = ?", params[:general_search] ]) end

  32. find • Conditions can be used as a string, array or hash • If you use strings, you should sanitize def self.authenticate_unsafely(user_name, password) find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'") end def self.authenticate_safely(user_name, password) find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ]) end def self.authenticate_safely_simply(user_name, password) find(:first, :conditions => { :user_name => user_name, :password => password }) end http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001696

  33. find_by_sql() • You can also use sql with the find_by_sql method • This does tie you to the db backend # A simple SQL query spanning multiple tables Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id" > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...] http://api.rubyonrails.com/classes/ActiveRecord/Base.html#M001696

  34. Refining a find if params[:general_search] #@target = params[:general_search] @machines = Machine.find(:all, \ :conditions => [ \ "user_name = ? OR \ from_entity = ? OR \ from_location = ? OR \ to_entity = ? OR \ to_location = ?", \ params[:general_search], params[:general_search], params[:general_search], params[:general_search], params[:general_search] ]) end • Here, we're using an array with Ors to search the basic text fields

  35. Combining code • Right now, this works because we're setting @machines first with a find that looks for records not exported to main, and then reset it if general_search is set in params • This is sloppy, we should combine them • So move the first find to an else

  36. if params[:general_search] @machines = Machine.find(:all, \ :conditions => [ "user_name = ? OR from_entity = ? OR from_location = ? OR to_entity = ? OR to_location = ?", params[:general_search], params[:general_search], params[:general_search], params[:general_search], params[:general_search] ]) else @machines = Machine.find( \ :all, \ :conditions => { :exported_to_main => false}) end

  37. One more thing :conditions => [ "user_name LIKE ? OR from_entity LIKE ? OR from_location LIKE ? OR to_entity LIKE ? OR to_location LIKE ?", "%#{params[:general_search]}%", "%#{params[:general_search]}%", "%#{params[:general_search]}%", "%#{params[:general_search]}%", "%#{params[:general_search]}%" ]) • Our search so far will only find exact matches • We can use a LIKE condition • Notice the use of % and #{}

  38. Advanced Search • We could probably do this with just a new view • But for grins, we'll create a new controller named search with an index page: ruby script/generate controller search index

  39. A simple page • Just to be able to test things out, we'll make a new page, ordered.html.erb in the views/search dir (which was created when we generated the controller) • This will just display some simple results, since we're just testing…

  40. ordered.html.erb <h1>Ordered Machines</h1> <% for machine in @machines -%> <%=h machine.user_name %><br/> <%=h machine.date_submitted.strftime "%m-%d-%Y" %><br/> Ticket: <%=h machine.ticket_number %><br/> From: <%=h machine.from_entity %> To: <%=h machine.to_entity %><br/> <%=h machine.comments %><br/> <hr/> <% end %>

  41. Try to load it • If we load the page using:http://127.0.0.1:3000/search/ordered • we get an error, no method • This is because our new controller has no methods defined

  42. a better find • find as a method is handy, but it's limited • For rails 1x, Nick Kallen wrote a plugin, has_finder, which extended searches • This functionality was incorporated into rails 2.x as named_scope • A class method, this will create a custom method with search conditions

  43. machine.erb • In the machine controller, we can create a named scope that finds machines that have been ordered • We can then call this from the search controller • We'll do more with named_scope next week--it's more flexible than find but also a bit confusing # Using named_scope, define a search that returns # records of machines that have been ordered named_scope :machines_ordered, :conditions => { :status => "ordered" }

  44. search.erb • In the search controller, we then set a variable • So when we load /search/ordered, the search controller accesses the method we defined in the machine controller, and passes the data to ordered.html.erb # This structure accesses a named scope # in machines_controller.erb @machines = Machine.machines_ordered

  45. Ok? • Yes, this is ok, but if we follow this approach through, we'll wind up with many pages • What would be better would be to use the search/index.html.erb file to allow selection, and then pass the data into the machine/index.html.erb file • So we start with the search/index.html.erb file • And we'll start with find…

  46. search/index.html.erb • Some text_field_tags and <h2>Advanced Search</h2> <h3>Choose Field</h3> <% form_tag machines_path, :method => 'get' do %> <p> <%= text_field_tag :search_target, params[:search] %> for <%= text_field_tag :search_string, params[:search] %> <%= submit_tag "Search", :name => nil %> </p> <% end %>

  47. search/index.html.erb • This gives us a simple form, for which we enter the field to search and the string to search for

  48. machine controller • Add an if to the controller, this time we'll use a find with slightly different syntax @machines = Machine.find( :all, :conditions => { params[:search_target] => params[:search_string]} ) if params[:search_target]

  49. A basic working search • So we have a basic form that has some flexibility for searches • Everything up to this point is in inventory02.gz.tar up in the samples dir

  50. A selection • What we have works, but it's awkward • For our advanced search we need a select • We'll start with an html select <select name="search_target"> <option value="user_name">User Name</option> <option value="from_entity">From Entity</option> <option value="from_location">From Location</option> <option value="to_entity">To Entity</option> <option value="to_location">To Location</option> </select>

More Related