# Af - an application framework gem 'fiksu-af' This application framework supports: * an Application class with supporting infrastruction * command line options integrated into instance (and class) variables * logging via log4r with support for papertrail * postgres advisory locking via pg_advisor_locker gem * postgres database connection updates via pg_application_name gem * threads and message passing * application components adding loggers and command line options ## Application class The class ::Af::Application provides a basic interface to the start-up and management of Ruby on Rails scripts. A simple example of an application which logs its startup and exits is: ``` class MyApplication < ::Af::Application opt :word, "the word", :short => :w, :default => "bird" def work logger.info "Started up: #{@word} is the word" exit 0 end end ``` Class-method "opt" specifies command line parameter(s) whose arguments are stored in instance variables. MyApplication specifies a single parameter "--word" with a short alias "-w" that accepts a string argument (whose default is "bird", so we know it should be a string). The parameters value will be stored in class instance variable: @word. More information on command line option glue is provided in the Command Line Options section. The method "logger" is available to ::Af::Application to create and fetch log4r loggers. In this case, we use the basic application logger (named "MyApplication") and logs an INFO level message. More information on the glue ::Af::Application provides with Log4r is in the logging section below. This application would be run from the command line as: ``` $ script/rails runner MyApplication.run --word grease ``` The class method #run provides start-up glue for applications, parsing command line options, environment variables and configuration files. The instance method #work is where application codes start their work. ## Command Line Options a more interesting example of options from multiple classes ``` class AfSideComponent include Af::Application::Component opt_group :side_component_stuff, "options associated with this side component" do opt :a_side_component_option, :default => "foo" end opt :basic_option_from_component, "this is a switch from the side component, in the basic (default) group" create_proxy_logger :foo def do_something foo_logger.info "doing something @@a_side_component_option='#{@@a_side_component_option}'" foo_logger.info "did something @@basic_option_from_component=#{@@basic_option_from_component.inspect}" end end ``` ``` class AfScriptWithOptions < ::Af::Application opt_group :singles, "parameters whose types are a single value" do opt :a_switch, "a switch" opt :an_int, "an integer of some sort", :default => 1 opt :a_float, "a float of some sort whose default is PI", :default => 3.14 opt :a_string, "a string of some sort", :type => :string opt :a_uri, "some uri", :type => :uri opt :a_date, "a date", :type => :date opt :a_time, "a time", :type => :time opt :a_datetime, "a datetime", :type => :datetime opt :a_choice, "a choice", :argument_note => "COLOR", :choices => [:red, :blue, :green, :yellow, :white, :black, :orange] end # there is already an advanced option group -- it is set to hidden by default opt_group :advanced, "advanced parameters", :hidden => false do opt :a_small_integer, "another integer", :default => 10, :argument_note => "[1 >= INTEGER <= 10]" do |argument,option| argument = argument.to_i if (argument < 1 || argument > 10) opt_error "ERROR: #{option.long_name}: value must be between 1 and 10 (inclusive)" end argument end opt :a_constrained_number, "a number which is not an integer", :requirement => :required, :argument_note => "NON-INTEGER" do |argument,option| i_argument = argument.to_i f_argument = argument.to_f if (i_argument == f_argument) opt_error "ERROR: #{option.long_name}: value must not be an integer" end f_argument end end opt_group :collections, "parameters whose types are collections" do opt :a_hash, "a key value pair", :type => :hash opt :some_ints, "a list of integer", :type => :ints opt :some_integers, "a list of integer", :type => :integers opt :some_floats, "a list of floats", :type => :floats opt :some_numbers, "a list of numbers", :type => :numbers opt :some_strings, "a list of strings", :type => :strings opt :some_uris, "a list of uris", :type => :uris opt :some_dates, "a list of dates", :type => :dates opt :some_times, "a list of times", :type => :times opt :some_datetimes, "a list of datetimes", :type => :datetimes opt :some_choices, "a list of choices", :choices => [:foo, :bar, :baz, :beltch] end opt_group :blank, "a blank group that won't be seen" opt :word, "the word to print", :default => :foo, :choices => [:foo, :bar, :baz, :beltch] opt :words, "the words to print", :default => [:foo], :choices => [:foo, :bar, :baz, :beltch] opt :numbers, "the number lists", :default => [1,2,3] opt :switcher, "this is a switch" def af_opt_class_path [AfSideComponent] + super end def logger super('Process::T') end def work logger.info "switcher: #{switcher.inspect}" logger.info "WORK STARTED: #{@word}" logger.info @words.inspect logger.info "NUMBERS: #{@numbers.inspect}" AfSideComponent.new.do_something logger.info "WORK COMPLETED: #{@word}" end end ``` ## Logging with Log4r Some changes to Log4r: * root loggers are no longer null loggers -- you can add outputters to them * the global logger is used to set a global log level (root is no longer used for this) * yaml configurator has been updated to handle new root logger semantics * yaml configurator has been updated to accept a set of files AND sections (default section: log4r_config) What this really means is that you can set up one outputter in the root logger to manage the default logging behavior. ## Thread Pool *TODO* ## TCP Message Passing *TODO*