wash

2005年10月8日 #

programming ruby 2nd--extending ruby 1

1.Ruby Object  in c
2.juke box extension
3.memory allocation
4.ruby type system
5.create an exception
6embed a ruby interpreter
7.bridge to other language
8.c api
1.
p269
Sometimes, though, life is more complicated. Perhaps you want to define a global variable
whose valuemust be calculated when it is accessed. You do this by defining hooked
and virtual variables. A hooked variable is a real variable that is initialized by a named
function when the corresponding Ruby variable is accessed. Virtual variables are similar
but are never stored: their value purely comes from evaluating the hook function.
See the API section that begins on page 294 for details.
If you create a Ruby object from C and store it in a C global variable without exporting
it to Ruby, you must at least tell the garbage collector about it, lest ye be reaped
inadvertently.
static VALUE obj;
// ...
obj = rb_ary_new();
rb_global_variable(obj);

posted @ 2006-06-15 14:53 wash 阅读(252) | 评论 (0)编辑 收藏

Primary Keys and IDs

You may have noticed that our sample database tables all define an integer
column called id as their primary key. This is an Active Record convention.
“But wait!” you cry. “Shouldn’t the primary key of my orders table be the
order number or some other meaningful column? Why use an artificial
primary key such as id?”
The reason is largely a practical one—the format of external data may
change over time. For example, you might think that the ISBN of a book
would make a good primary key in a table of books. After all, ISBNs are
Report erratum Prepared exclusively for Don Francis
PRIMARY KEYS AND IDS 198
unique. But as this particular book is being written, the publishing industry
in the US is gearing up for a major change as additional digits are
added to all ISBNs.
If we’d used the ISBN as the primary key in a table of books, we’d have to
go through and update each row to reflect this change. But then we’d have
another problem. There’ll be other tables in the database that reference
rows in the books table via the primary key. We can’t change the key in the
books table unless we first go through and update all of these references.
And that will involve dropping foreign key constraints, updating tables,
updating the books table, and finally reestablishing the constraints. All in
all, something of a pain.
If we use our own internal value as a primary key, things work out a lot
better. No third party can come along and arbitrarily tell us to change
things—we control our own keyspace. And if something such as the ISBN
does need to change, it can change without affecting any of the existing
relationships in the database. In effect, we’ve decoupled the knitting
together of rows from the external representation of data in those rows.
Now there’s nothing to say that we can’t expose the id value to our end
users. In the orders table, we could externally call it an order id and print
it on all the paperwork. But be careful doing this—at any time some regulator
may come along and mandate that order ids must follow an externally
imposed format, and you’d be back where you started.
If you’re creating a new schema for a Rails application, you’ll probably
want to go with the flow and give all of your tables an id column as their
primary key. If you need to work with an existing schema, Active Record
gives you a simple way of overriding the default name of the primary key
for a table.
class BadBook < ActiveRecord::Base
set_primary_key "isbn"
end
Normally, Active Record takes care of creating new primary key values
for records that you create and add to the database—they’ll be ascending
integers (possibly with some gaps in the sequence). However, if you override
the primary key column’s name, you also take on the responsibility
of setting the primary key to a unique value before you save a new row.
Perhaps surprisingly, you still set an attribute called id to do this. As far as
As we’ll see later, join tables are not included in this advice—they should not have an id column.
Active Record is concerned, the primary key attribute is always set using
an attribute called id. The set_primary_key declaration sets the name of the
column to use in the table. In the following code, we use an attribute
called id even though the primary key in the database is isbn.
book = BadBook.new
book.id = "0-12345-6789"
book.title = "My Great American Novel"
book.save
# ...
book = BadBook.find("0-12345-6789")
puts book.title # => "My Great American Novel"
p book.attributes #=> {"isbn" =>"0-12345-6789",
"title"=>"My Great American Novel"}
Just to make things more confusing, the attributes of the model object
have the column names isbn and title—id doesn’t appear. When you need
to set the primary key, use id. At all other times, use the actual column
name.

posted @ 2006-05-11 11:41 wash 阅读(198) | 评论 (0)编辑 收藏

Accessing Attributes

If a model object has an attribute named balance, you can access the
attribute’s value using the indexing operator, passing it either a string or
a symbol. Here we’ll use symbols.
account[:balance] #=> return current value
account[:balance] = 0.0 #=> set value of balance
However, this is deprecated in normal code, as it considerably reduces
your options should you want to change the underlying implementation
of the attribute in the future. Instead, you should access values or model
attributes using Ruby accessor methods.
account.balance #=> return current value
account.balance = 0.0 #=> set value of balance
The value returned using these two techniques will be cast by Active
Record to an appropriate Ruby type if possible (so, for example, if the
database column is a timestamp, a Time object will be returned). If you
want to get the raw value of an attribute, append _before_type_cast to the
method form of its name, as shown in the following code.

COLUMNS AND ATTRIBUTES 195
David Says. . .
Overriding Model Attributes
Here’s an example of the benefits of using accessors to get at the
attributes of models. Our account model will raise an exception immediately
when someone tries to set a balance below a minimum value.
class Account < ActiveRecord::Base
def balance=(value)
raise BalanceTooLow if value < MINIMUM_LEVEL
self[:balance] = value
end
end
account.balance_before_type_cast #=> "123.4", a string
account.release_date_before_type_cast #=> "20050301"
Finally, inside the code of the model itself, you can use the read_attribute( )
and write_attribute( ) private methods. These take the attribute name as a
string parameter.
Boolean Attributes
Some databases support a boolean column type, others don’t. This makes
it hard for Active Record to abstract booleans. For example, if the underlying
database has no boolean type, some developers use a char(1) column
containing “t” or “f” to represent true or false. Others use integer columns,
where 0 is false and 1 is true. Even if the database supports boolean types
directly (such as MySQL and its bool column type), they might just be
stored as 0 or 1 internally.
The problem is that in Ruby the number 0 and the string “f” are both
interpreted as true values in conditions.4 This means that if you use the
value of the column directly, your code will interpret the column as true
when you intended it to be false.
# DON'T DO THIS
user = Users.find_by_name("Dave")
if user.superuser
grant_privileges
end
4Ruby has a simple definition of truth. Any value that is not nil or the constant false is
true.

To query a column in a condition, you must append a question mark to
the column’s name.
# INSTEAD, DO THIS
user = Users.find_by_name("Dave")
if user.superuser?
grant_privileges
end
This form of attribute accessor looks at the column’s value. It is interpreted
as false only if it is the number zero; one of the strings "0", "f", "false",
or "" (the empty string); a nil; or the constant false. Otherwise it is interpreted
as true.
If you work with legacy schemas or have databases in languages other than
English, the definition of truth in the previous paragraph may not hold.
In these cases, you can override the built-in definition of the predicate
methods. For example, in Dutch, the field might contain J or N (for Ja or
Nee). In this case, you could write
class User < ActiveRecord::Base
def superuser?
self.superuser == 'J'
end
# . . .
end
Storing Structured Data
It is sometimes convenient to store attributes containing arbitrary Ruby
objects directly into database tables. One way that Active Record supports
this is by serializing the Ruby object into a string (in YAML format) and
storing that string in the database column corresponding to the attribute.
In the schema, this column must be defined as type text.
Because Active Record will normally map a character or text column to a
plain Ruby string, you need to tell Active Record to use serialization if you
want to take advantage of this functionality. For example, we might want
to record the last five purchases made by our customers. We’ll create a
table containing a text column to hold this information.
File 6 create table purchases (
id int not null auto_increment,
name varchar(100) not null,
last_five text,
primary key (id)
);
In the Active Record class that wraps this table, we’ll use the serialize( )
declaration to tell Active Record to marshal objects into and out of this
column.
File 8 class Purchase < ActiveRecord::Base
serialize :last_five
# ...
end
When we create new Purchase objects, we can assign any Ruby object to
the last_five column. In this case, we set it to an array of strings.
File 8 purchase = Purchase.new
purchase.name = "Dave Thomas"
purchase.last_five = [ 'shoes', 'shirt', 'socks', 'ski mask', 'shorts' ]
purchase.save
When we later read it in, the attribute is set back to an array.
File 8 purchase = Purchase.find_by_name("Dave Thomas")
pp purchase.last_five
pp purchase.last_five[3]
This code outputs
["shoes", "shirt", "socks", "ski mask", "shorts"]
"ski mask"
Although powerful and convenient, this approach is problematic if you
ever need to be able to use the information in the serialized columns outside
a Ruby application. Unless that application understands the YAML
format, the column contents will be opaque to it. In particular, it will be
difficult to use the structure inside these columns in SQL queries. You
might instead want to consider using object aggregation, described in Section
15.2, Aggregation, on page 247, to achieve a similar effect.

posted @ 2006-05-11 11:09 wash 阅读(195) | 评论 (0)编辑 收藏

Active Record Basics

Active Record is the object-relational mapping (ORM) layer supplied with
Rails. In this chapter, we’ll look at the basics of Active Record—connecting
to databases, mapping tables, and manipulating data. We’ll dig deeper
into the more advanced stuff in the next chapter.
Active Record closely follows the standard ORM model: tables map to
classes, rows to objects, and columns to object attributes. It differs from
most other ORM libraries in the way it is configured. By using a sensible
set of defaults, Active Record minimizes the amount of configuration that
developers perform. To illustrate this, here’s a program that uses Active
Record to wrap a table of orders in a MySQL database. After finding the
order with a particular id, it modifies the purchaser’s name and saves the
result back in the database, updating the original row.

require "rubygems"
require_gem "activerecord"
ActiveRecord::Base.establish_connection(:adapter => "mysql",
:host => "localhost", :database => "railsdb")
class Order < ActiveRecord::Base
end
order = Order.find(123)
order.name = "Dave Thomas"
order.save

That’s all there is to it—in this case no configuration information (apart
from the database connection stuff) is required. Somehow Active Record
figured out what we needed and got it right. Let’s have a look at how this
works.

14.1 Tables and Classes
When you create a subclass of ActiveRecord::Base, you’re creating something
that wraps a database table. By default, Active Record assumes that
the name of the table is the plural form of the name of the class. If the class
name contains multiple capitalized words, the table name is assumed to
have underscores between these words. Some irregular plurals are handled.
Class Name
Order
TaxAgency
Diagnosis
Batch
Table Name
tax_agencies
orders
batches
diagnoses
LineItem
Person
Datum
Quantity
Class Name
line_items
people
quantities
data
Table Name
These rules reflect DHH’s philosophy that class names should be singular
while the names of tables should be plural. If you don’t like this behavior,
you can disable it by setting a global flag in your configuration (the file
environment.rb in the config directory).
ActiveRecord::Base.pluralize_table_names = false
The algorithm used to derive the plural form of a table name is fairly simplistic.
It works in the majority of common cases, but if you have a class
named Sheep, it’ll valiantly try to find a table named sheeps. The assumption
that the table name and class names are related might also break
down if you’re operating with a legacy schema,2 where the table names
might otherwise force you to use strange or undesirable class names in
your code. For this reason, Active Record allows you to override the default
generation of a table name using the set_table_name directive.


14.2 Columns and Attributes
Active Record objects correspond to rows in a database table. The objects
have attributes corresponding to the columns in the table. You probably
noticed that our definition of class Order didn’t mention any of the columns
in the orders table. That’s because Active Record determines them dynamically
at runtime. Active Record reflects on the schema inside the database
to configure the classes that wrap tables.3
Our orders table might have been created with the following SQL.
File 6 create table orders (
id int not null auto_increment,
name varchar(100) not null,
email varchar(255) not null,
address text not null,
pay_type char(10) not null,
shipped_at datetime null,
primary key (id)
);

posted @ 2006-05-10 11:12 wash 阅读(279) | 评论 (0)编辑 收藏

Logging in Rails and Debugging Hints and use breakpoint

 Logging in Rails
Rails has logging built right into the framework. Or, to be more accurate,
Rails exposes a Logger object to all the code in a Rails application.
Logger is a simple logging framework that ships with recent versions of
Ruby. (You can get more information by typing ri Logger at a command
prompt or by looking in the standard library documentation in Programming
Ruby [TH01]). For our purposes, it’s enough to know that we can
generate log messages at the warning, info, error, and fatal levels. We can
then decide (probably in an environment file) which levels of logging to
write to the log files.
logger.warn("I don't think that's a good idea")
logger.info("Dave's trying to do something bad")
logger.error("Now he's gone and broken it")
logger.fatal("I give up")
In a Rails application, these messages are written to a file in the log directory.
The file used depends on the environment in which your application
is running. A development application will log to log/development.log, an
application under test to test.log, and a production app to production.log.

13.7 Debugging Hints
Bugs happen. Even in Rails applications. This section has some hints on
tracking them down.
First and foremost, write tests! Rails makes it easy to write both unit
tests and functional tests (as we saw in Chapter 12, Task T: Testing, on
page 132). Use them, and you’ll find that your bug rate drops way down.
You’ll also decrease the likelihood of bugs suddenly appearing in code that
you wrote a month ago. Tests are cheap insurance.

Tests tell you whether something works or not, and they help you isolate
the code that has a problem. Sometimes, though, the cause isn’t immediately
apparent.
If the problem is in a model, you might be able to track it down by running
the offending class outside the context of a web application. The
scripts/console script lets you bring up part of a Rails application in an irb
session, letting you experiment with methods. Here’s a session where we
use the console to update the price of a product.
depot> ruby script/console
Loading development environment.
irb(main):001:0> pr = Product.find(:first)
=> #<Product:0x248acd0 @attributes={"image_url"=>"/images/sk..."
irb(main):002:0> pr.price
=> 29.95
irb(main):003:0> pr.price = 34.95
=> 34.95
irb(main):004:0> pr.save
=> true
Logging and tracing are a great way of understanding the dynamics of
complex applications. You’ll find a wealth of information in the development
log file. When something unexpected happens, this should probably
be the first place you look. It’s also worth inspecting the web server log for
anomalies. If you use WEBrick in development, this will be scrolling by on
the console you use to issue the script/server command.
You can add your own messages to the log with Logger object described in
the previous section. Sometimes the log files are so busy that it’s hard to
find the message you added. In those cases, and if you’re using WEBrick,
writing to STDERR will cause your message to appear on the WEBrick console,
intermixed with the normal WEBrick tracing..
If a page comes up displaying the wrong information, you might want to
dump out the objects being passed in from the controller. The debug( )
helper method is good for this. It formats objects nicely and makes sure
that their contents are valid HTML.
<h3>Your Order</h3>
<%= debug(@order) %>
<div id="ordersummary">
. . .
</div>
Finally, for those problems that just don’t seem to want to get fixed, you
can roll out the big guns and point a debugger at your running application.
This is normally available only for applications in the development
environment.

To use breakpoints:
1. Insert a call to the method breakpoint( ) at the point in your code where
you want your application to first stop. You can pass this method a
string if you’d like—this becomes an identifying message later.
2. On a convenient console, navigate to your application’s base directory
and enter the command
depot> ruby script/breakpointer
No connection to breakpoint service at
druby://localhost:42531 (DRb::DRbConnError)
Tries to connect will be made every 2 seconds...
Don’t worry about the No connection message—it just means that
your breakpoint hasn’t hit yet.
3. Using a browser, prod your application to make it hit the breakpoint( )
method. When it does, the console where breakpointer is running will
burst into life—you’ll be in an irb session, talking to your running
web application. You can inspect variables, set values, add other
breakpoints, and generally have a good time. When you quit irb, your
application will continue running.
By default, breakpoint support uses a local network connection to talk
between your application and the breakpointer client. You might be able to
use the -s option when you run breakpointer to connect to an application on
another machine.

posted @ 2006-05-10 10:51 wash 阅读(278) | 评论 (0)编辑 收藏

Active Support

Active Support is a set of libraries that are shared by all Rails components.
Much of what’s in there is intended for Rails internal use. However, Active
Support also extends some of Ruby’s built-in classes in interesting and
useful ways. In this section we’ll quickly list the most popular of these
extensions.
Extensions to Numbers
Class Fixnum gains the two instance methods even? and odd?.
All numeric objects gain a set of scaling methods.
puts 20.bytes #=> 20
puts 20.kilobytes #=> 20480
puts 20.megabytes #=> 20971520
puts 20.gigabytes #=> 21474836480
puts 20.terabytes #=> 21990232555520
There are also time-based scaling methods. These convert their receiver
into the equivalent number of seconds. The months( ) and years( ) methods
are approximations—months are assumed to be 30 days long, years 365
days long.
puts 20.minutes #=> 1200
puts 20.hours #=> 72000
puts 20.days #=> 1728000
puts 20.weeks #=> 12096000
puts 20.fortnights #=> 24192000
puts 20.months #=> 51840000
puts 20.years #=> 630720000
You can also calculate times relative to Time.now using the methods ago( )
and from_now( ) (or their aliases until( ) and since( ), respectively).
puts Time.now #=> Tue May 10 17:03:43 CDT 2005
puts 20.minutes.ago #=> Tue May 10 16:43:43 CDT 2005
puts 20.hours.from_now #=> Wed May 11 13:03:43 CDT 2005
puts 20.weeks.from_now #=> Tue Sep 27 17:03:43 CDT 2005
puts 20.months.ago #=> Thu Sep 18 17:03:43 CDT 2003
How cool is that?
Time Extensions
The Time class gains a number of useful methods, helping you calculate
relative times.
now = Time.now
puts now #=> Tue May 10 17:15:59 CDT 2005
puts now.ago(3600) #=> Tue May 10 16:15:59 CDT 2005
puts now.at_beginning_of_day #=> Tue May 10 00:00:00 CDT 2005
puts now.at_beginning_of_month #=> Sun May 01 00:00:00 CDT 2005
puts now.at_beginning_of_week #=> Mon May 09 00:00:00 CDT 2005
puts now.at_beginning_of_year #=> Sat Jan 01 00:00:00 CST 2005
puts now.at_midnight #=> Tue May 10 00:00:00 CDT 2005
puts now.change(:hour => 13) #=> Tue May 10 13:00:00 CDT 2005
puts now.last_month #=> Sun Apr 10 17:15:59 CDT 2005
puts now.last_year #=> Mon May 10 17:15:59 CDT 2004
puts now.midnight #=> Tue May 10 00:00:00 CDT 2005
puts now.monday #=> Mon May 09 00:00:00 CDT 2005
puts now.months_ago(2) #=> Thu Mar 10 17:15:59 CST 2005
puts now.months_since(2) #=> Sun Jul 10 17:15:59 CDT 2005
puts now.next_week #=> Mon May 16 00:00:00 CDT 2005
puts now.next_year #=> Wed May 10 17:15:59 CDT 2006
puts now.seconds_since_midnight #=> 62159.215938
puts now.since(7200) #=> Tue May 10 19:15:59 CDT 2005
puts now.tomorrow #=> Wed May 11 17:15:59 CDT 2005
puts now.years_ago(2) #=> Sat May 10 17:15:59 CDT 2003
puts now.years_since(2) #=> Thu May 10 17:15:59 CDT 2007
puts now.yesterday #=> Mon May 09 17:15:59 CDT 2005
Active Support also includes a TimeZone class. TimeZone objects encapsulate
the names and offset of a time zone. The class contains a list of the
world’s time zones. See the Active Support RDoc for details.
String Extensions
Active Support adds methods to all strings to support the way the Rails
core converts names from singular to plural, lowercase to mixed case, and
so on. Of these, two might be useful in the average application.
puts "cat".pluralize #=> cats
puts "cats".pluralize #=> cats
puts "erratum".pluralize #=> errata
puts "cats".singularize #=> cat
puts "errata".singularize #=> erratum

posted @ 2006-05-10 10:35 wash 阅读(196) | 评论 (0)编辑 收藏

Naming Conventions

The rules here are the default conventions used by Rails. You can override
all of these conventions using the appropriate declarations in your Rails
classes.
We often name variables and classes using short phrases. In Ruby, the
convention is to have variable names where the letters are all lowercase,
and words are separated by underscores. Classes and modules are named
differently: there are no underscores, and each word in the phrase (including
the first) is capitalized. (We’ll call this mixed-case, for fairly obvious
reasons). These conventions lead to variable names such as order_status
and class names such as LineItem.

Rails takes this convention and extends it in two ways. First, it assumes
that database table names, like variable names, have lowercase letters and
underscores between the words. Rails also assumes that table names are
always plural. This leads to table names such as orders and third_parties.
On another axis, Rails assumes that files are named in lowercase with
underscores.
Rails uses this knowledge of naming conventions to convert names automatically.
For example, your application might contain a model class that
handles line items. You’d define the class using the Ruby naming convention,
calling it LineItem. From this name, Rails would automatically deduce
the following.


That the corresponding database table will be called line_items. That’s
the class name, converted to lowercase, with underscores between
the words and pluralized.
• Rails would also know to look for the class definition in a file called
line_item.rb (in the app/models directory).

Rails controllers have additional naming conventions. If our application
has a store controller, then the following happens.
• Rails assumes the class is called StoreController and that it’s in a file
named store_controller.rb in the app/controllers directory.
• It also assumes there’s a helper module named StoreHelper in the file
store_helper.rb located in the app/helpers directory.
• It will look for view templates for this controller in the app/views/store
directory.
• It will by default take the output of these views and wrap them in the
layout template contained in store.rhtml or store.rxml in the directory
app/views/layouts.
There’s one extra twist. In normal Ruby code you have to use the require
keyword to include Ruby source files before you reference the classes and
modules in those files. Because Rails knows the relationship between
filenames and class names, require is not necessary in a Rails application.
Instead, the first time you reference a class or module that isn’t known,
Rails uses the naming conventions to convert the class name to a filename
and tries to load that file behind the scenes. The net effect is that you can

Model Naming
URL http://.../store/list
File app/views/store/list.rhtml (or .rxml)
View Naming
Helper module StoreHelper
File app/helpers/store_helper.rb
URL http://.../store/list
Class StoreController
File app/controllers/store_controller.rb
Controller Naming
Method list()
Layout app/views/layouts/store.rhtml
Figure 13.3: Naming Convention Summary

typically reference (say) the name of a model class, and that model will be
automatically loaded into your application.
As you’ll see, this scheme breaks down when your classes are stored in
sessions. In this case you’ll need to explicitly declare them. Even so, you
don’t use require. Instead, your controller would include a line such as
class StoreController < ApplicationController
model :line_item
# ...
Notice how the naming conventions are still used consistently here. The
symbol :line_item is lowercase with an underscore. It will cause the file
line_item.rb to be loaded, and that file will contain class LineItem.

Grouping Controllers into Modules
So far, all our controllers have lived in the app/controllers directory. It is
sometimes convenient to add more structure to this arrangement. For
example, our store might end up with a number of controllers performing
related but disjoint administration functions. Rather than pollute the top-
level namespace with each of these, we might choose to group them into a
single admin namespace.
Rails does this using a simple convention. If an incoming request has a
controller named (say) admin/book, Rails will look for the controller called
book_controller in the directory app/controllers/admin. That is, the final part
of the controller name will always resolve to a file called name_controller.rb,
and any leading path information will be used to navigate through subdirectories,
starting in the app/controllers directory.
Imagine that our application has two such groups of controllers (say,
admin/xxx and content/xxx) and that both groups defined a book controller.
There’d be a file called book_controller.rb in both the admin and content subdirectories
of app/controllers. Both of these controller files would define a
class named BookController. If Rails took no further steps, these two classes
would clash.
To deal with this, Rails assumes that controllers in subdirectories of the
directory app/controllers are in Ruby modules named after the subdirectory.
Thus, the book controller in the admin subdirectory would be declared as
class Admin::BookController < ApplicationController
# ...
end

The book controller in the content subdirectory would be in the Content
module.
class Content::BookController < ApplicationController
# ...
end
The two controllers are therefore kept separate inside your application.
The templates for these controllers appear in subdirectories of app/views.
Thus, the view template corresponding to the request
http://my.app/admin/book/edit/1234
will be in the file
app/views/admin/book/edit.rhtml
You’ll be pleased to know that the controller generator understands the
concept of controllers in modules and lets you create them with commands
such as
myapp> ruby script/generate controller Admin::Book action1 action2 ...
This pattern of controller naming has ramifications when we start generating
URLs to link actions together. We’ll talk about this starting on
page 287.

posted @ 2006-05-10 10:28 wash 阅读(199) | 评论 (0)编辑 收藏

rails enviroment

The runtime configuration of your application is performed by two files.
One, config/environment.rb, is environment independent—it is used regardless
of the setting of RAILS_ENV.
The second file does depend on the environment:
Rails looks for a file named for the current environment in the
directory config/environments and loads it during the processing of environment.
rb.
The standard three environments (development.rb, production.rb,
and test.rb) are included by default. You can add your own file if you’ve
defined new environment types.
Environment files typically do three things.
• They set up the Ruby load path. This is how your application can
find things such as models and views when it’s running.
• They create resources used by your application (such as the logger).
• They set various configuration options, both for Rails and for your
application.

The first two of these are normally application-wide and so are done in
environment.rb. The configuration options often vary depending on the environment
and so are likely to be set in the environment-specific files in the
environments directory.

The Load Path
The standard environment automatically includes the following directories
(relative to your application’s base directory) into your application’s load
path.
1. test/mocks/environment. As these are first in the load path, classes
defined here override the real versions, enabling you to replace live
functionality with stub code during testing. This is described starting
on page 161.
2. All directories whose names start with an underscore or a lowercase
letter under app/models and components.子目录
3.The directories app, app/models, app/controllers, app/helpers, app/apis,
components, config, lib, vendor, and vendor/rails/*.
Each of these directories is added to the load path only if it exists.

Application-wide Resources

environment.rb creates an instance of a Logger that will log messages to
log/environment.log. It sets this to be the logger used by Active Record,
Action Controller, and Action Mailer (unless your environment-specific
configuration files had already set their own logger into any of these components).
environment.rb also tells Action Controller and Mailer to use app/views as
the starting point when looking for templates. Again, this can be overridden
in the environment-specific configurations.

Configuration Parameters
You configure Rails by setting various options in the Rails modules. Typically
you’ll make these settings either at the end of environment.rb (if you
want the setting to apply in all environments) or in one of the environmentspecific
files in the environments directory.
We provide a listing of all these configuration parameters in Appendix B

posted @ 2006-05-10 09:41 wash 阅读(195) | 评论 (0)编辑 收藏

service engine guide

Services which are used in different applications can be defined only once by creating Global Service Definition files or services specific to an application can be restricted and available only to that application.
When used in a web application services are available to web events, which allow events to stay small and reuse existing logic in the Services Framework. Also, services can be defined as 'exportable' which means they are allowed to be accessed by outside parties.
Currently there is a SOAP EventHandler which allows services to be made available via SOAP. Other forms of remote invocation may be added to the framework in the future

posted @ 2006-01-26 16:12 wash 阅读(254) | 评论 (0)编辑 收藏

ofbiz entity engine guide

The primary goal of the entity engine is to eliminate the need for entity specific persistence code in as many areas of a transactional application as possible. Granted that this sort of abstraction is a different issue for reporting and similar systems, but for transactional systems such as are used on a day to day basis in all businesses, an entity engine can save a great deal of development effort and dramatically reduce persistence related bugs in the system. These types of applications include everything from ecommerce to accounting to inventory and warehouse management to human resources and so on. These tools can be useful for reporting and analytical systems,

but really aren't meant to allow for the wide variety of custom queries that often take place in such tools.???


In order to achieve having as little entity specific code as possible, all value objects are generic, using a map to store the fields values of the entity instance by name. The get and set methods for the fields take a String with the fieldName in it which is used to verify that the field is part of the entity, and then either get or set a value as desired.
 
The danger of this flexibility is curtailed using a contract between the entity engine and the application; this is contained in a special XML file.???


Instead of writing entity specific code, entity definitions are read from an XML file and used by the entity engine to enforce a set of rules between the application and the data source, be it a database or some other source. These XML entity definitions specify the names of all of the entities and their fields along with which database tables and columns they correspond to. They are also used to specify a type for each field which is then looked up in the field types file for the given data source to find the Java and SQL data types. Relations between entities are also defined in this XML file. A relation is defined by specifying the related table, the type of relation (one or many) and the keymaps for the relation. A title can also be given to the relation which becomes part of its name to distinguish it from other relations to that specific related entity. 

Using the Entity Engine as an abstraction layer, entity specific code can be easily created and modified. Code that uses the Entity Engine APIs to interact with entities can be deployed in various ways so that entity persistence can be done differently without changing the code that interacts with those entities on a higher level. An example of the usefulness of this abstraction is that, by changing a configuration file, an application written using the Entity Engine can switch from hitting a database directly through JDBC to using an EJB server and Entity Beans for persistence. The same code could also be used for custom data sources like legacy systems over HTTP or messaging services through a bit of custom coding within the same framework.


posted @ 2006-01-24 14:48 wash 阅读(269) | 评论 (0)编辑 收藏

Kid Language Specification

Kid is a simple XML based template language that uses embedded Python to do cool stuff. The syntax was inspired by a number of existing template languages, namely XSLT, TAL, and PHP.

This document describes the template language and will be most useful as reference to those developing Kid templates. For information about using templates from Python, the command line, or in web environments, see the User's Guide.

1   Synopsis

<?python
title = "A Kid Test Document"
fruits = ["apple", "orange", "kiwi", "M&M"]
from platform import system
?>
<html xmlns:py="http://purl.org/kid/ns#">
  <head>
    <title py:content="title">This is replaced.</title>
  </head>
  <body>
    <p>These are some of my favorite fruits:</p>
    <ul>
      <li py:for="fruit in fruits">
        I like ${fruit}s
      </li>
    </ul>
    <p py:if="system() == 'Linux'">
      Good for you!
    </p>
  </body>
</html>

Yields something like this:

<?xml version="1.0" encoding="utf-8"?>
<html>
  <head>
    <title>A Kid Test Document</title>
  </head>
  <body>
    <p>These are some of my favorite fruits:</p>
    <ul>
      <li>I like apples</li>
      <li>I like oranges</li>
      <li>I like kiwis</li>
      <li>I like M&amp;Ms</li>
    </ul>
    <p>
      Good for you!
    </p>
  </body>
</html>

2   The Kid Namespace

All attributes described in this document must belong to the following namespace:

http://purl.org/kid/ns#

The namespace prefix py is used throughout this document to indicate that an item belongs to the Kid/Python namespace.

3   Embedding Code Blocks (<?python?>)

The <?python?> processing instruction (PI) contains Python code and MAY occur anywhere that is legal for processing instructions to occur in an XML document.

The rules for executing code found in a <?python?> PI is as follows:

  1. <?python?> PIs located outside of the document element (e.g. root element) contain Document Level code. This code SHOULD be executed in a global, shared scope for the document. The code SHOULD be executed once when the template is loaded and shared between multiple invocations of the template.
  2. <?python?> PIs located within the document element contain Local Level code. This code is executed each time the document is processed with a local scope specific to the invocation and the shared document level global scope.

Document Level and Local Level code work exactly like Module Level and Function Level code in normal Python modules. For example, the following Kid template:

<?python
x = 0
y = 0
?>
<html xmlns:py="http://purl.org/kid/ns#">
  <?python
  x = 1
  if x == 1:
    x = 10
  ?>
  <p py:content="x"/>
  <?python
  global y
  y = 30
  ?>
  <p py:content="y"/>
</html>

May be considered equivalent to the following Python module:

x = 0
y = 0
def expand(handler):
  handler.startDocument()
  handler.startElement('html')
  x = 1
  if x == 1:
    x = 10
  handler.element('p', content=x) # output p element with x as value
  global y
  y = 30
  handler.element('p', content=y) # output p element with value of y
  handler.endElement('html')
  handler.endDocument()

<?python?> PIs may contain any legal Python language construct including functions, classes, lamda forms, etc.

<?python
class Adder:
  def __init__(self, x, y):
    self.x = x
    self.y = y
  def doit(self):
    return self.x + self.y

foo = Adder(x=400, y=20)
x = foo.doit()
?>

Single line <?python?> PIs are okay too:

<?python x = 10 ?>

4   Content Producing Constructs

There are multiple methods of generating content output from a template: py:content, py:replace, py:attrs, and ${} substitution. Each of these syntaxes have the same rules for what types of objects may result from the Python expression they contain.

str, unicode
The string is inserted as XML CDATA. That is, it is non-parsable character data that does not contain markup. The following characters are encoded as XML entities when serialized: '<', '&'. Attribute values containing content also encode the quote character: '"'.
ElementTree.Element

When an ElementTree.Element is referenced from a content producing construct, the item is inserted into the document literally, i.e. it is not encoded as text, but becomes part of the output structure.

The XML() and document() functions can be used to turn a string into structured content and to retrieve an XML document from a URL, respectively.

Note that attribute values MUST NOT reference structured content. This applies to py:attrs and using ${} substitution in attribute values.

sequence
If a sequence type (list, tuple, or other iterable) is referenced, the rules are applied to each of the items in the sequence. For example, you could reference a list containing an Element and a string.
Other
If the result of evaluating the expression is any other type, an attempt is made to coerce the value to unicode as if by calling unicode(expr) and processing continues as if the object were a string or unicode object initially.

5   Python Expression Substitution (${expr})

Attributes not belonging to the Kid namespace and text content MAY embed Python expressions by enclosing the expression within a dollar sign followed by curly braces: ${expr}. The result of evaluating the expression(s) is substituted with the rest of the attribute value or text content following rules defined for Content Producing Constructs.

<?python
verb = 'ran'
noun = 'store'
?>
<a title="I ${verb} to the ${noun}">...

... would result in:

<a title="I ran to the store">...

If an attribute value consists purely of substitution expressions and all expressions evaluate to None, the attribute is removed. This can be avoided by using expr or '' to force a zero length string to be returned instead of None. For example:

<?python
# set something to None
x = None
?>
<a title="${x}">...

... would result in:

<a>...

However, this:

<?python x = None?>
<a title="${x or ''}">...

... results in:

<a title="">...

5.1   Identifier Shortcut ($name)

For simple expressions consisting entirely variable names and object access operators (.), the curly braces may be omitted:

<a href="http://example.com/$page" title="$title">
   Dots are allowed too: $object.another.attribute
</a>

However, it is good practice to use the curly brace form as it sets the substitution off from the other text a bit more providing a stronger visual clue as to what's going on.

5.2   Escaping ($$)

$$ is an escape. $${bla} will output ${bla}.

6   Default Imports

All templates have a few default imports for convenience.

6.1   XML() function

Expression substitution, py:content, and py:replace encode strings as text. That is, text is encoded according to the rules of the XML specification, which includes, among other things, replacing the literal characters < and & with their encoded counterparts (&lt; &amp;). If you have XML stored as a string and want it to be output as XML and not encoded text, you need to pass the string to the XML function.

For example, let's say there is a function, hello, that returns XML data that should be embedded in template output (let's say it returns <hello>world</hello>). Consider the following:

<p>${hello()}</p>

The result would be:

<p>&lt;hello>world&lt;hello></p>

Calling the XML function would have given us the result we intended:

<p>${XML(hello())}</p>
<p><hello>world</hello></p>

6.2   document() function

The document function loads an XML document from a file or URL allowing it to be embedded in template output:

<div py:content="document('header.html')"></div>

The document function resolves paths relative to the current template file (if the template location is available).

7   Attribute Language

7.1   Repetition/Iteration (py:for)

<element py:for="target_list in expression_list" />

Works exactly like the Python for statement.

The py:for attribute may appear on any element to signify that the element should be processed multiple times, once for each value in the sequence specified:

<?python
bottles = range(1, 101)
bottles.reverse()
?>
<p py:for="num in bottles">
   <span py:content="num">X</span> bottles of beer on the wall,
   <span py:content="num">X</span> bottles of beer on the wall,
   take one down, pass it around, <span py:content="num - 1">X - 1</span>
   bottles of beer on the wall.
</p>

The py:for attribute is the first attribute to be processed if present. All other py: attributes are processed for each iteration of the loop.

7.2   Conditionals (py:if)

<element py:if="expr" />

The py:if attribute may appear on any element to signify that the element and its decendant items should be output only if the boolean expression specified evaluates to true in Python:

<p py:if="5 * 5 == 25">
  Python seems to be handling multiplication okay.
</p>

The py:if attribute is processed after the py:for attribute and is evaluated for each iteration. If the result of evaluating expr as a boolean expression is false in Python, no further py: attributes are processed for the current iteration or, if not in a py:for, at all.

Note

Evaluated as a boolean expression in Python, None, False, [], (), {}, 0, and '' are all considered to be false.

7.3   Dynamic Content (py:content)

<element py:content="expr" />

This attribute MAY appear on any element to signify that the decendant items of the element are to be replaced with the result of evaluating expr.

<p py:content="time.strftime('%C %c')">The Time</p>

Results in:

<p>Tues, Jun 26, 2004 02:03:53 AM</p>

py:content is a Content Producing Construct and can output both character and structured data.

7.4   Replacing Content (py:replace)

<element py:replace='expr' />

py:replace is shorthand for specifying a py:content and a py:strip="True" on the same element:

<?python
x = 10
?>
<p><span py:replace="x">...</span></p>

... results in:

<p>10</p>

... and is equivelant to specifying:

<?python #
x = 10
?>
<p><span py:strip="" py:content="x">...</span></p>

The py:replace attribute is processed after the py:for and py:if attributes. py:strip and py:content attributes are not processed and are discarded.

py:replace is a Content Producing Construct and can output both character and structured data.

7.5   Stripping Tags (py:strip)

<element py:strip="expr" />

The py:strip attribute may apppear on any element to signify that the containing element should not be output. If the attribute value is blank (no expr at all) or if the result expr is a boolean expression that evaluates to true, the element is not output, but all descendant elements are processed normally. If expr is not blank and the result of evaluating expr as a boolean expression is false, processing continues as if the attribute did not exist.

The py:strip attribute MAY appear on an element with any other kid attribute. However, if both a py:replace and a py:strip exist on the same element, the py:strip attribute is ignored and discarded.

The py:strip attribute is processed after the py:for and py:if attributes. If omission is eminent, the py:content attribute is processed normally but attribute interpolation does not occur.

7.6   Dynamic Attributes (py:attrs)

<element py:attrs="expr" />

The py:attrs attribute may appear on any element to specify a set of attributes that should be set on the element when it is processed. The expression specified MUST evaluate to one of the following types of values:

dict
A dictionary with keys specifying attribute names and values specifying attribute values. These are added to the attributes of the current element by calling element.attrib.update(mapping), where element is an ElementTree Element object and mapping is the dictionary returned from the expression. Outer curly braces are not necessary to write down.
list
A list of tuples of the form (name, value) is also acceptable. Each item of the list is added to the current set of attributes by iterating over the list and calling element.set(name, value).
keyword arguments
The attributes can also be specified as comma separated keyword arguments of the form name=value.

The following lines:

<elem py:attrs="{'a':1, 'ns:b':2}" />
<elem py:attrs="'a':1, 'ns:b':2" />
<elem py:attrs="(('a',1), ('ns:b',2))" />
<elem py:attrs="a=1, ns:b=2" />

will all produce the same output:

<elem a="1" ns:b="2" />

Note that attributes whose values are None will be removed. If a blank attribute is desired, an empty string should be used.

If the expression specified is an empty dictionary or an empty list, the attributes are not modified in any way.

py:attrs is a Content Producing Construct, but can output only character data.

7.7   Named Template Functions (py:def)

<element py:def="template_name(arg_list)" />

The py:def attribute may appear on any element to create a "Named Template Function". Markup contained within an py:def element is not output during normal template expansion but can be referenced from other Content Producing Constructs to insert the markup at the point referenced.

Like normal Python functions, Named Template Functions have an optional argument list that may use all of the jazzy features of Python argument lists like variable and keyword arguments.

Named Template Functions are invoked exactly like normal Python functions. They are generally invoked from Content Producing Constructs like py:content or ${} substitution.

<ul py:def="display_list(seq)">
   <li py:for="item in seq" py:content="item" />
</ul>

<table py:def="display_dict(mapping)">
   <tr>
       <th>Key</th>
       <th>Value</th>
   </tr>
   <tr py:for="key, value in mapping.items()">
       <td py:content="key" />
       <td py:content="value" />
   </tr>
</table>

Here we've defined two Named Template Functions: display_list and display_dict. The first function takes a sequence and the second a mapping. We can invoke these functions from the same template by invoking them from a content producing construct:

<body>
   ${display_list(['apple', 'orange', 'kiwi'])}

   <div py:replace="display_dict({'x' : 'y', 'p' : 'q'})">
    Key/Value Table replaces this text
   </div>
</body>

7.8   Match Templates (py:match)

<element py:match="expr" />

The py:match attribute may appear on any element to create a "Match Template". Markup contained within a Match Template element is not output during normal template expansion. Instead, these constructs set up filters for expansion output that are capable of transforming content as it is generated.

Match Templates are generally used to insert content dynamically based on patterns in template expansion or to provide "custom tag" functionality similar to that found in JSP taglibs or XSLT.

A Match Template has two parts: the match expression part (expr) and the body part (the element and it's descendants).

Match Templates are processed as follows:

  1. Each element that is output from a template goes through the Match Template Filter.
  2. The Match Template Filter visits each of the Match Templates defined in the current template and the templates the current template extends in the order that they are defined and evaluates the associated match expression.
  3. If the match expression returns true as a boolean expression, the match template's body is expanded and replaces the original element and all of its descendants.

In both the match expression and in the match template's body, the item name is bound to the Element that is being output. However, there are some limitations to what can be accessed at each phase:

  1. During match expression evaluation, only the item Element and none of its descendants are available. This means that match expressions are limited to testing matches based on the immediate Element's tag and attributes [1].
  2. During match template expansion (that is, when the match expression is true), the element's descendants are available and may be referenced from Content Producing Constructs to output bits and pieces of the matched items structure.
[1] This is due to the streaming nature of the Kid processor. During normal template expansion, the entire tree is never fully retained in memory.

7.8.1   Example

The following simple example shows how to create a custom tag <greeting> that outputs one of two provided values based on the time of day the template is expanded:

<?xml version="1.0" encoding="utf-8"?>
<?python
from time import localtime
def timeofday():
    """Get time of day ('am' or 'pm')"""
    return localtime().tm_hour < 12 and 'am' or 'pm'
?>
<html xmlns:py="http://purl.org/kid/ns#">
  <!-- define the greeting match template -->
  <span py:match="item.tag == 'greeting'"
        py:replace="item.get(timeofday())">
  </span>

  <head>
    <title>Time of day demo</title>
  </head>
  <body>
    <p>
      Good <greeting am="Morning!" pm="Afternoon" />
    </p>
  </body>
</html>

An important thing to note is that the py:match expression and the match template body have access to the <greeting> element via the variable item. The item.get(timeofday()) bit retrieves the value of the am attribute or the pm attribute based on what is returned from the timeofday function.

At 9:00 AM, output from this template would look like this:

<html>
  <head>
    <title>Time of day demo</title>
  </head>
  <body>
    <p>
      Good Morning!
    </p>
  </body>
</html>

The obvious question at this point is how to reuse Match Templates? The example above demonstrates the use of a Match Template from the same main template but it is often desirable to have "libraries" of Match Templates that could be used by multiple individual templates. The answer is to have the main template extend a common template containing the Match Templates needed. We can rewrite the above example as two separate templates: main.kid and common.kid.

The common template would look like this:

<?xml version="1.0" encoding="utf-8"?>
<?python
from time import localtime
def timeofday():
    """Get time of day ('am' or 'pm')"""
    return localtime().tm_hour < 12 and 'am' or 'pm'
?>
<html xmlns:py="http://purl.org/kid/ns#">
  <!-- define the greeting match template -->
  <span py:match="item.tag == 'greeting'"
        py:replace="item.get(timeofday())">
  </span>
</html>

And the main template would look like this:

<?xml version="1.0" encoding="utf-8"?>
<html py:extends="'common.kid'">
  <head>
    <title>Time of day demo</title>
  </head>
  <body>
    <p>
      Good <greeting am="Morning!" pm="Afternoon" />
    </p>
  </body>
</html>

When a template extends another template (or set of templates), all of the Match Templates and Named Template Functions of the extended templates are available as if they were defined locally.

Warning

Match templates are an experimental feature. Syntax and semantics may change significantly or be removed entirely in future release. Actually, this statement applies to many aspects of Kid but this one is especially unstable.

7.9   Template Reuse (py:extends)

<root py:extends="template1, template2, ...">

The py:extends attribute may appear on the root element to specify that the template should inherit the Named Template Functions and Match Templates defined in another template (or set of templates). If a py:extends attribute is specified, it MUST be on the root element of the document.

The py:extends may contain a list of Python expressions separated by commas that reference templates. The rules for what types of values may be specified are:

string

The name of a template file, relative to the current template file.

Example:

<html py:extends="'common.kid'" />
module or Template class

The py:extends variable references a module or a Template class. If a module is referenced, an attempt is made to find a class named Template belonging to the that module.

Example:

<?python
import common
?>
<html py:extends="common" ...

Multiple templates may be referenced by separating each by a comma. The following example references templates common and forms, imported using the import hooks and a template filename named other.kid:

<?python
import common, forms
?>
<html py:extends="common, forms, 'other.kid'" ...

7.9.1   Example

For example, there is a template named common.kid that defines a template function, display_errors, and a match template that converts <b> elements to <strong> elements with uppercase content:

<html xmlns:py="http://purl.org/kid/ns#">

  <ul py:def="display_errors(errors)">
    <li py:for="error in errors" py:content="error" />
  </ul>

  <strong py:match="item.tag == 'b'"
    py:content="item.text.upper()" />

</html>

The functions and match templates may be imported into another template by referencing them with py:extends:

<html py:extends="'common.kid'"
      xmlns:py="http://purl.org/kid/ns#">
  <head>
    <title>Errors</title>
  </head>
  <body>
    <p>The following <b>errors</b> were found:</p>
    ${ display_errors(["Field is required", "Must be phone number.."]) }
  </body>
</html>

The <b>errors</b> item is transformed to <strong>ERRORS</strong> and the error list is displayed. Both the match template and the named template function are available in the derived template as if they were defined locally.

8   Processing Order

The order that py: attributes are processed is as follows:

  1. py:def
  2. py:match
  3. py:for
  4. py:if
  5. py:replace
  6. py:strip
  7. py:attrs
  8. py:content

Attribute substitution occurs after all other

posted @ 2005-12-26 16:11 wash 阅读(165) | 评论 (0)编辑 收藏

cherrypy documentation

     摘要: . Application developer reference AbstractCherryPy lets developers use Python to develop web applications, just as they would use Python for any other type of application. Building a web appli...  阅读全文

posted @ 2005-12-26 09:02 wash 阅读(1377) | 评论 (0)编辑 收藏

turbo gears get start

     摘要: Your model objects represent the data that your application is working with. Your controller pulls in information coming from the user's web browser. It uses that information to update information i...  阅读全文

posted @ 2005-12-21 09:24 wash 阅读(258) | 评论 (0)编辑 收藏

webworks chapter 1 -夏昕Webwork2 Developer’s Guide Version 1.0


ServletDispatcher 接受到Servlet Container 传递过来的请求,将进行一下几个动作:
1. 从请求的服务名(/login.action)中解析出对应的Action名称(login)
2. 遍历HttpServletRequest、HttpSession、ServletContext 中的数据,并将其复制到
Webwork的Map实现中,至此之后,所有数据操作均在此Map结构中进行,从
而将内部结构与Servlet API相分离。
至此,Webwork 的工作阶段结束,数据将传递给XWork 进行下一步处理。从这里也可以看到Webwork和xwork之间的切分点,Webwork为xwork提供了一个面向Servlet 的协议转换器,将Servlet 相关的数据转构转换成xwork所需要的通用数据格式,而xwork将完成实际的服务调度和功能实现。
这样一来,以xwork为核心,只需替换外围的协议转换组件,即可实现不同技术平台之间的切换(如将面向Servlet的Webwork替换为面向JMS的协议转换器实现,即可在保留应用逻辑实现的情况下,实现不同外部技术平台之间的移植)。
3. 以上述信息作为参数,调用ActionProxyFactory创建对应的ActionProxy实例。
ActionProxyFactory 将根据Xwork 配置文件(xwork.xml)中的设定,创建
ActionProxy实例,ActionProxy中包含了Action的配置信息(包括Action名称,
对应实现类等等)。
4. ActionProxy创建对应的Action实例,并根据配置进行一系列的处理程序。包括
执行相应的预处理程序(如通过Interceptor 将Map 中的请求数据转换为Action
所需要的Java 输入数据对象等),以及对Action 运行结果进行后处理。
ActionInvocation 是这一过程的调度者。而com.opensymphony.xwork.
DefaultActionInvocation 则是XWork 中对ActionInvocation 接口的标准实现,如
果有精力可以对此类进行仔细研读,掌握了这里面的玄机,相信XWork的引擎
就不再神秘。
下面我们来看配置文件:
xwork.xml:
<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">
<
xwork>
<include file="webwork-default.xml" /> ⑴
<package name="default" extends="webwork-default"> ⑵
<action name="login" ⑶
class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher"> ⑷
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
<param name="location">/index.jsp</param>
</result>
<interceptor-ref name="params" /> ⑸
<interceptor-ref name="model-driven"/> ⑹
</action>
</package>
</xwork>
⑴ include
通过include 节点,我们可以将其他配置文件导入到默认配置文件xwork.xml 中。
从而实现良好的配置划分。
这里我们导入了Webwork 提供的默认配置webwork-default.xml(位于
webwork.jar 的根路径)。
⑵ package
XWork中,可以通过package对action进行分组。类似Java 中package和class的
关系。为可能出现的同名Action提供了命名空间上的隔离。
同时,package还支持继承关系。在这里的定义中,我们可以看到:
extends="webwork-default"
"webwork-default"是webwork-default.xml文件中定义的package,这里通
过继承,"default" package 自动拥有"webwork-default" package 中的所有
定义关系。这个特性为我们的配置带来了极大便利。在实际开发过程中,我们可以根据自身的应用特点,定义相应的package模板,并在各个项目中加以重用,无需再在重复繁琐的配置过程中消耗精力和时间。
此外,我们还可以在Package节点中指定namespace,将我们的action分为若干个
逻辑区间。如:
<package name="default" namespace="/user"
extends="webwork-default">
就将此package中的action定义划归为/user 区间,之后在页面调用action的时候,
我们需要用/user/login.action 作为form action 的属性值。其中的/user/就指定了此
action的namespace,通过这样的机制,我们可以将系统内的action进行逻辑分类,
从而使得各模块之间的划分更加清晰。
⑶ action
Action配置节点,这里可以设定Action的名称和对应实现类。
⑷ result
通过result 节点,可以定义Action 返回语义,即根据返回值,决定处理模式以及
响应界面。
这里,返回值"success"(Action 调用返回值为String 类型)对应的处理模式为
"dispatcher"。
可选的处理模式还有:
1. dispatcher
本系统页面间转向。类似forward。
2. redirect
浏览器跳转。可转向其他系统页面。
3. chain
将处理结果转交给另外一个Action处理,以实现Action的链式处理。
4. velocity
将指定的velocity模板作为结果呈现界面。
5. xslt
将指定的XSLT 作为结果呈现界面。
随后的param节点则设定了相匹配的资源名称。
⑷ interceptor-ref
设定了施加于此Action的拦截器(interceptor)。关于拦截器,请参见稍后的“XWork拦截器体系”部。
interceptor-ref定义的是一个拦截器的应用,具体的拦截器设定,实际上是继
承于webwork-default package,我们可以在webwork-default.xml 中找到
对应的"params"和"model-driven"拦截器设置:
<interceptors>
……
<interceptor name="params"class="com.opensymphony.xwork.interceptor.ParametersInt
erceptor" />
<interceptor name="model-driven"
class="com.opensymphony.xwork.interceptor.ModelDrivenIn
terceptor" />
……
</interceptors>
"params"大概是Webwork 中最重要、也最常用的一个Interceptor。上面曾经将
MVC工作流程划分为几个步骤,其中的第一步:
“将Web 页面中的输入元素封装为一个(请求)数据对象”
就是通过"params"拦截器完成。Interceptor 将在Action 之前被调用,因而,
Interceptor 也成为将Webwork传来的MAP 格式的数据转换为强类型Java 对象的
最佳实现场所。
"model-driven"则是针对Action 的Model驱动模式的interceptor 实现。具体描
述请参见稍后的“Action驱动模式”部分很可能我们的Action 都需要对这两个interceptor 进行引用。我们可以定义一个interceptor-stack,将其作为一个interceptor 组合在所有Action 中引用。如,上面的配置文件可修改为:
<xwork>
<include file="webwork-default.xml" />
<package name="default" extends="webwork-default">
<interceptors>
<interceptor-stack name="modelParamsStack">
<interceptor-ref name="params" />
<interceptor-ref name="model-driven" />
</interceptor-stack>
</interceptors>
<action name="login" class="net.xiaxin.webwork.action.LoginAction">
<result name="success" type="dispatcher">
<param name="location">/main.jsp</param>
</result>
<result name="loginfail" type="dispatcher">
<param name="location">/index.jsp</param>
</result>
<interceptor-ref name="modelParamsStack" />
</action>
</package>
</xwork>
通过引入interceptor-stack,我们可以减少interceptor 的重复申明。
下面是我们的Model对象:
LoginInfo.java:
public class LoginInfo {
private String password;
private String username;
private List messages = new ArrayList();
private String errorMessage;
public List getMessages() {
return messages;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}
很简单,这只是一个纯粹的值对象(Value-Object)。这里,它扮演着模型(Model)的角色,并与Action的输入输出密切相关。与Spring MVC中的Command对象不同,Webwork 中的Model对象,扮演着承上启下的角色,它既是Action的输入参数,又包含了Action处理的结果数据。
换句话说,输入的Http请求参数,将被存储在Model对象传递给Action进行处理,Action处理完毕之后,也将结果数据放置到Model 对象中,之后,Model 对象与返回界面融合生成最后的反馈页面。也正由于此,笔者建议在实际开发中采用Model-Driven 模式,而非Property-Driven 模式(见稍后“Action驱动模式”部分),这将使得业务逻辑更加清晰可读。
对应的Action代码
public class LoginAction implements Action, ModelDriven {
private final static String LOGIN_FAIL="loginfail";
LoginInfo loginInfo = new LoginInfo();
public String execute() throws Exception {
if ("erica".equalsIgnoreCase(loginInfo.getUsername())
&& "mypass".equals(loginInfo.getPassword())) {
//将当前登录的用户名保存到Session
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
//出于演示目的,通过硬编码增加通知消息以供显示
loginInfo.getMessages().add("message1");
loginInfo.getMessages().add("message2");
loginInfo.getMessages().add("message3");
return SUCCESS;
}else{
loginInfo.setErrorMessage("Username/Password Error!");
return LOGIN_FAIL;
}
}
public Object getModel() {
return loginInfo;
}
}
可以看到,LoginAction实现了两个接口:
1. Action
Action接口非常简单,它指定了Action的入口方法(execute),并定义了
几个默认的返回值常量:
public interface Action extends Serializable {
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public String execute() throws Exception;
}
private final static String LOGIN_FAIL="loginfail";
LoginInfo loginInfo = new LoginInfo();
public String execute() throws Exception {
if ("erica".equalsIgnoreCase(loginInfo.getUsername())
&& "mypass".equals(loginInfo.getPassword())) {
//将当前登录的用户名保存到Session
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
//出于演示目的,通过硬编码增加通知消息以供显示
loginInfo.getMessages().add("message1");
loginInfo.getMessages().add("message2");
loginInfo.getMessages().add("message3");
return SUCCESS;
}else{
loginInfo.setErrorMessage("Username/Password Error!");
return LOGIN_FAIL;
}
}
public Object getModel() {
return loginInfo;
}
}
可以看到,LoginAction实现了两个接口:
1. Action
Action接口非常简单,它指定了Action的入口方法(execute),并定义了
几个默认的返回值常量:
public interface Action extends Serializable {
public static final String SUCCESS = "success";
public static final String NONE = "none";
public static final String ERROR = "error";
public static final String INPUT = "input";
public static final String LOGIN = "login";
public String execute() throws Exception;
}
SUCCESS、NONE、ERROR、INPUT、LOGIN 几个字符串常量定义了常用的
几类返回值。我们可以在Action 实现中定义自己的返回类型,如本例中的
LOGIN_FAIL定义。
而execute方法,则是Action的入口方法,XWork将调用每个Action的execute
方法以完成业务逻辑处理。
2. ModelDriven
ModelDriven接口更为简洁:
public interface ModelDriven {
Object getModel();
}
ModelDriven仅仅定义了一个getModel方法。XWork在调度Action时,将通
过此方法获取Model 对象实例,并根据请求参数为其设定属性值。而此后的
页面返回过程中,XWork 也将调用此方法获取Model 对象实例并将其与设定
的返回界面相融合。
注意这里与Spring MVC 不同,Spring MVC 会自动为逻辑处理单元创建
Command Class实例,但Webwork不会自动为Action创建Model对象实例,
Model 对象实例的创建需要我们在Action 代码中完成(如LoginAction 中
LoginInfo对象实例的创建)。
另外,如代码注释中所描述,登录成功之后,我们随即将username保存在Session之中,这也是大多数登录操作必不可少的一个操作过程。
这里面牵涉到了Webwork中的一个重要组成部分:ActionContext。
ActionContext为Action提供了与容器交互的途径。对于Web 应用而言,与容器的交互大多集中在Session、Parameter,通过ActionContext我们在代码中实现与Servlet API无关的容器交互。
如上面代码中的:
ActionContext ctx = ActionContext.getContext();
Map session = ctx.getSession();
session.put("username",loginInfo.getUsername());
同样,我们也可以操作Parameter:
ActionContext ctx = ActionContext.getContext();
Map params = ctx.getParameters();
String username = ctx.getParameters("username");
上述的操作,将由XWork根据当前环境,调用容器相关的访问组件(Web 应用对应的就是Webwork)完成。上面的ActionContext.getSession(),XWork 实际上将通过Webwork提供的容器访问代码“HttpServletRequest.getSession()”完成。
注意到,ActionContext.getSession返回的是一个Map类型的数据对象,而非HttpSession。这是由于WebWork对HttpSession进行了转换,使其转变为与Servlet API无关的Map对象。通过这样的方式,保证了Xwork 所面向的是一个通用的开放结构。从而使得逻辑层与表现层无关。增加了代码重用的可能。此外, 为了提供与Web 容器直接交互的可能。WebWork 还提供了一个ServletActionContext实现。它扩展了ActionContext,提供了直接面向Servlet API的容器访问机制。
我们可以直接通过ServletActionContext.getRequest 得到当前HttpServletRequest 对象的引用,从而直接与Web 容器交互。
获得如此灵活性的代价就是,我们的代码从此与ServletAPI 紧密耦合,之后系统在不同平台之间移植就将面临更多的挑战(同时单元测试也难于进行)。
平台移植的需求并不是每个应用都具备。大部分系统在设计阶段就已经确定其运行平台,且无太多变更的可能。不过,如果条件允许,尽量通过ActionContext 与容器交互,而不是平台相关的ServletActionContext,这样在顺利实现功能的同时,也获得了平台迁移上的潜在优势,何乐而不为。
登录成功界面:
main.jsp:
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<p align="center">Login Success!</p>
<p align="center">Welcome!
<ww:property value="#session['username']"/>
</p>
<p align="center">
<b>Messages:</b><br>
<ww:iterator value="messages" status="index">
<ww:if test="#index.odd == true">
!<ww:property/><br>
</ww:if>
<ww:else>
*<ww:property/><br>
</ww:else>
</ww:iterator>
</p>
</body>
</html>
这里我们引入了Webwork的taglib,如页面代码第一行的申明语句。
下面主要使用了三个tag:
Ø <ww:property value="#session['username']"/>
读取Model对象的属性填充到当前位置。
value指定了需要读取的Model对象的属性名。
这里我们引用了LoginAction在session中保存的’username’对象。
由于对应的Model(LoginInfo)中也保存了username 属性。下面的语句与之
效果相同:
<ww:property value="username"/>
与JSP2 中的EL类似,对于级联对象,这里我们也可以通过“.”操作符获得
其属性值,如value="user.username"将得到Model 对象中所引用的user
对象的username 属性(假设LoginInfo中包含一个User 对象,并拥有一个名
为“username”的属性)。
关于EL的内容比较简单,本文就不再单独开辟章节进行探讨。
Webwork中包括以下几种特殊的EL表达式:
² parameter[‘username’] 相当于request.getParameter(“username”);
² request[‘username’] 相当于request.getAttribute(“username”);
² session[‘username’] 从session中取出以“username”为key的值
² application[‘username’] 从ServletContext中取出以“username”为key
的值
注意需要用“#”操作符引用这些特殊表达式。
另外对于常量,需要用单引号包围,如#session['username'] 中的
'username'。
Ø <ww:iterator value="messages" status="index">
迭代器。用于对java.util.Collection、java.util.Iterator、java.util.Enumeration,、
java.util.Map、Array类型的数据集进行循环处理。
其中,value属性的语义与<ww:property>中一致。
而status属性则指定了循环中的索引变量,在循环中,它将自动递增。
而在下面的<ww:if>中,我们通过“#”操作符引用这个索引变量的值。
索引变量提供了以下几个常用判定方法:
² first 当前是否为首次迭代
² last 当前是否为最后一次迭代
² odd 当前迭代次数是否奇数
² even 当前迭代次数是否偶数
Ø <ww:if test="#index.odd == true">和<ww:else>
用于条件判定。
test属性指定了判定表达式。表达式中可通过“#”操作符对变量进行引用。
表达式的编写语法与java 表达式类似。
类似的,还有<ww:elseif test="……">。

登录失败界面
实际上,这个界面即登录界面index.jsp。只是由于之前出于避免干扰的考虑,隐藏了index.jsp中显示错误信息的部分。
完整的index.jsp如下:
<%@ page pageEncoding="gb2312"
contentType="text/html;charset=gb2312"%>
<%@ taglib prefix="ww" uri="webwork"%>
<html>
<body>
<form action="/login.action">
<p align="center">
登录<br>
<ww:if test="errorMessage != null">
<font color="red">
<ww:property value="errorMessage"/>
</font>
</ww:if>
</p>
用户名:
<input type="text" name="model.username" />
<br>
密码:
<input type="password" name="model.password" />
<br>
<p align="center">
<input type="submit" value="提交" name="B1"/>
<input type="reset" value="重置" name="B2"/>
</p>
</form>
</body>
</html>
这里首先我们进行判断,如果Model中的errorMessage不为null,则显示错误信息。这样,在用户第一次登录时,由于Model对象尚未创建,errorMessage自然为null,错误信息不会显示,即得到了与之前的index.jsp同样的效果。

posted @ 2005-10-08 11:23 wash 阅读(537) | 评论 (0)编辑 收藏