torsdag, augusti 17, 2006

The JRuby Tutorial #2: Going Camping

So, for the 2nd, (actually third installment, but the JIRB one doesn't really count), I was thinking that we should take a look at a few different subjects. The red thread will be a simple web based Blog application built with Camping. Camping is a microframework for web development, by whytheluckystuff, is insanely small and incredibly powerful. It uses another library called Markaby, which generates markup based on pure Ruby code. I will show the application in smaller parts, with explanations, and at the end include a link to the complete source file.

First of all we have to have a working JRuby built from the latest version of trunk. After JRuby is working we need to install a few gems. Follow these commands and you'll be good:
jruby %JRUBY_HOME%\bin\gem install camping --no-ri --no-rdoc --include-dependencies
jruby %JRUBY_HOME%\bin\gem install ActiveRecord-JDBC --no-ri --no-rdoc
This installs Camping, ActiveRecord, ActiveSupport, Markaby, Builder, Metaid and ActiveRecord-JDBC. We don't generate RDoc or RI for these gems, since that's one part of JRuby that still is pretty slow.

The Blog application
The first thing the blog application needs is a database. I will use MySQL, but other databases may work through the JDBC-adapter, but there is still some work to be done in this area. I will have my MySQL server on localhost, so change the configuration if you do not have this setup. You'll need a database for the blog application. I've been conservative and named the database "blog" and the user "blog" with the password "blog". Easy to remember, but not so good for production.

Update: As azzoti mentioned, you have to set your classpath to include the MySQL JDBC-driver, which can be downloaded from http://www.mysql.org/.

Now, open up a new Ruby file and call it blog.rb. The name of the file is important; it has to have the same name as the Camping application. Now, first of all we include the dependencies:
require 'rubygems'
require 'camping'
require 'camping/session'
require_gem 'ActiveRecord-JDBC'
require 'active_record/connection_adapters/jdbc_adapter'
require 'java'

include_class 'java.lang.System'
These statements include Rubygems, Camping, Camping session support, ActiveRecord-JDBC and Java support. It also includes the Java class named java.lang.System for later use. The next step is to include some configuration for our application.
Camping.goes :Blog
Blog::Models::Base.establish_connection :adapter => 'jdbc', :driver => 'com.mysql.jdbc.Driver',
:url => 'jdbc:mysql://localhost:3306/blog', :username => 'blog', :password => 'blog'

module Blog
include Camping::Session
end
These statements first names our application with the Camping.goes-statement. This includes some fairly heavy magic, including reopening the file and rewriting the source to include more references to the Camping framework. But this line is all that is needed. The next line establishes our connection to the database, and it follows standard JDBC naming of the parameters. Of course, these should be in a YAML file somewhere, but for now we make it easy. The last part makes sure we have Session support in our Blog application.

Now we need to define our model, and this is easily done since Camping uses ActiveRecord:
module Blog::Models
def self.schema(&block)
@@schema = block if block_given?
@@schema
end

class Post < Base; belongs_to :user; end
class Comment < Base; belongs_to :user; end
class User < Base; end
end
The first part of this code defines a helper method that either sets the schema to the block given, or returns an earlier defined schema. The second part defines our model, which includes Post, Comment and User, and their relationships.

The schema is also part of the application, and we'll later see that Camping can automatically create it if it doesn't exist (that's why we didn't need to create any tables ourselves, just the database).
 Blog::Models.schema do
create_table :blog_posts, :force => true do |t|
t.column :id, :integer, :null => false
t.column :user_id, :integer, :null => false
t.column :title, :string, :limit => 255
t.column :body, :text
end
create_table :blog_users, :force => true do |t|
t.column :id, :integer, :null => false
t.column :username, :string
t.column :password, :string
end
create_table :blog_comments, :force => true do |t|
t.column :id, :integer, :null => false
t.column :post_id, :integer, :null => false
t.column :username, :string
t.column :body, :text
end
end
This defines the three tables needed by our blog system. Note that the names of the tables include the name of the application as a prefix. This is because Camping expects more than one application to be deployed in the same container, using the same database.

When we have defined the schema, it's time to define our controller actions. In Camping, each action is a class, and each action class define a method for get, one for post, etc. These classes will be defined inside the module Blog::Controllers. The first action we create will be the Index action. It looks like this:
     class Index < R '/'
def get
@posts = Post.find :all
render :index
end
end
This defines a class that inherits from an anonymous class defined by the R method. What it really does, is bind the Index action to the /-path. It uses ActiveRecord to get all posts and then renders the view with the name index.

The Add-action adds a new Post, but only if there is a user in the @state-variable, which acts as a session. If something is posted to it, it creates a new Post from the information and redirects to the View-action:
     class Add
def get
unless @state.user_id.blank?
@user = User.find @state.user_id
@post = Post.new
end
render :add
end
def post
post = Post.create :title => input.post_title, :body => input.post_body,
:user_id => @state.user_id
redirect View, post
end
end
As you can see, there's not much to it. Instance variables in the controller will be available to the view later on. Note that this action doesn't inherit from any class at all. This means it will only be available by an URL with it's name in it.

We need a few more controllers. View and Edit are for handling Posts. Comment adds new comments to an existing Post. Login and Logout are pretty self explanatory. And Style returns a stylesheet for all pages. Note that Style doesn't render anything, it just sets @body to a string with the contents to return.
     class View < R '/view/(\d+)'
def get post_id
@post = Post.find post_id
@comments = Models::Comment.find :all, :conditions => ['post_id = ?', post_id]
render :view
end
end

class Edit < R '/edit/(\d+)', '/edit'
def get post_id
unless @state.user_id.blank?
@user = User.find @state.user_id
end
@post = Post.find post_id
render :edit
end

def post
@post = Post.find input.post_id
@post.update_attributes :title => input.post_title, :body => input.post_body
redirect View, @post
end
end

class Comment
def post
Models::Comment.create(:username => input.post_username,
:body => input.post_body, :post_id => input.post_id)
redirect View, input.post_id
end
end

class Login
def post
@user = User.find :first, :conditions => ['username = ? AND password = ?', input.username, input.password]
if @user
@login = 'login success !'
@state.user_id = @user.id
else
@login = 'wrong user name or password'
end
render :login
end
end

class Logout
def get
@state.user_id = nil
render :logout
end
end

class Style < R '/styles.css'
def get
@headers["Content-Type"] = "text/css; charset=utf-8"
@body = %{
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
body {
font-family: Utopia, Georga, serif;
}
h1.header {
background-color: #fef;
margin: 0; padding: 10px;
}
div.content {
padding: 10px;
}
div.post {
background-color: #ffa;
border: 1px solid black;
padding: 20px;
margin: 10px;
}
}
end
end
Also note how easy it is to define routing rules with the help of regular expressions to the R method.

Next up, we have to create our views. Since Camping uses Markaby, we do it in Ruby, in the same file. Views are methods in the module Blog::Views with the same name as referenced inside the controllers call to render. There is a special view called layout which get called for all views, if you don't specify otherwise in the call to render. It looks like this:
     def layout
html do
head do
title 'Blog'
link :rel => 'stylesheet', :type => 'text/css',
:href => '/styles.css', :media => 'screen'
end
body do
h1.header { a 'Blog', :href => R(Index) }
div.content do
self << yield
end
end
end
end
As you can see, standard HTML tags are defined by calling a method by that name. The contents of the tag is created inside the block sent to that method, and if it makes sense to give it content as an argument, this works too. Title, for example. If you give a block to it, it will evaluate this and add the result as the title, but in this case it's easier to just provide a string. Note how a link is created, by the method R (another method R this time, since this is in the Views module). Finally, the contents of the layout gets added by appending the result of a yield to self.

The index view is the first we'll see when visiting the application, and it looks like this:
     def index
if @posts.empty?
p 'No posts found.'
else
for post in @posts
_post(post)
end
end
p { a 'Add', :href => R(Add) }
p "Current time in millis is #{System.currentTimeMillis}."
end
I've also added a call that writes out the current time in milliseconds, from Java's System class, to show that we're actually in Java land now, and potentially could base much of our application on data from Java. We check if there are any posts, and if so iterate over them and write them out with a partial called _post. We also create a link to add more posts. The rest of the views look like this:
     def login
p { b @login }
p { a 'Continue', :href => R(Add) }
end

def logout
p "You have been logged out."
p { a 'Continue', :href => R(Index) }
end

def add
if @user
_form(post, :action => R(Add))
else
_login
end
end

def edit
if @user
_form(post, :action => R(Edit))
else
_login
end
end

def view
_post(post)

p "Comment for this post:"
for c in @comments
h1 c.username
p c.body
end

form :action => R(Comment), :method => 'post' do
label 'Name', :for => 'post_username'; br
input :name => 'post_username', :type => 'text'; br
label 'Comment', :for => 'post_body'; br
textarea :name => 'post_body' do; end; br
input :type => 'hidden', :name => 'post_id', :value => post.id
input :type => 'submit'
end
end
And the three partials:
     def _login
form :action => R(Login), :method => 'post' do
label 'Username', :for => 'username'; br
input :name => 'username', :type => 'text'; br

label 'Password', :for => 'password'; br
input :name => 'password', :type => 'text'; br

input :type => 'submit', :name => 'login', :value => 'Login'
end
end

def _post(post)
div.post do
h1 post.title
p post.body
p do
a "Edit", :href => R(Edit, post)
a "View", :href => R(View, post)
end
end
end

def _form(post, opts)
p do
text "You are logged in as #{@user.username} | "
a 'Logout', :href => R(Logout)
end
form({:method => 'post'}.merge(opts)) do
label 'Title', :for => 'post_title'; br
input :name => 'post_title', :type => 'text',
:value => post.title; br

label 'Body', :for => 'post_body'; br
textarea post.body, :name => 'post_body'; br

input :type => 'hidden', :name => 'post_id', :value => post.id
input :type => 'submit'
end
end
In my opinion, this code is actually much easier to read than HTML and most of it is fairly straight forward. One interesting part is the add and edit methods, which checks if a user is logged in, otherwise uses the _login-partial instead of showing the real content.

Finally, we will define a create-method for Camping, which is responsible for creating the tables for our model:
 def Blog.create
Camping::Models::Session.create_schema
unless Blog::Models::Post.table_exists?
ActiveRecord::Schema.define(&Blog::Models.schema)
Blog::Models::User.create(:username => 'admin', :password => 'camping')
end
end

This first creates a table for the session information, and then checks if the Post-table exists; if not all the tables in the schema defined before is created.

Now you have seen the complete application. If you have no interest in writing this by hand, the complete code can be found here.

Running the application
To run a Camping application, you need to run the camping executable that has been installed into your %JRUBY_HOME%\bin on your application file. In my case I run it like this:
jruby %JRUBY_HOME%\bin\camping blog.rb
in the directory where my blog.rb exists and I very soon have a nice application at http://localhost:3301/blog which works wonderfully. Startup is a little bit slow, but as soon as WEBrick has started listening the application is very snappy. You can try changing your blog.rb-file too; Camping will automatically update your application without having to restart the server. As I said above, I included a call to System.currentTimeMillis, to show that we are actually using Java in this blog-application. If that isn't apparent from the call to System, remember that we are actually using JDBC to talk to our database, and very soon you will be able to use the ActiveRecord-JDBC adapter to connect to any databases Java can talk too. That's a brigth future.

74 kommentarer:

TimA sa...

Works a treat Ola! fantastic stuff.

If using MySql you'll need the MySql jdbc connector jar from http://dev.mysql.com/downloads/connector/j/3.1.html
unzip somewhere and setup on the classpath, for example for me on windows:

set CLASSPATH=C:\dev\mysql-connector-java-3.1.13\mysql-connector-java-3.1.13-bin.jar

Unknown sa...

I got a error running blog.rb


C:\jruby\bin>jruby .\camping blog.rb
c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:144:in `max': stack level to
o deep (SystemStackError)
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:144:in `<=>'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:184:in `>'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:184
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/specification.rb:10:in `ca
ll'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:281:in `satisfy
?'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:271:in `satisfi
ed_by?'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/source_index.rb:187:in `al
l?'
from c:/jruby/lib/ruby/site_ruby/1.8/rubygems/version.rb:272:in `each'
... 376 levels...
from c:/jruby/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_supp
ort/dependencies.rb:140:in `load'
from c:/jruby/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_supp
ort/dependencies.rb:140:in `load'
from .\camping:18

any idea?

Ola Bini sa...

Purpureleaf: I'm not totally sure what could cause that. Are you sure you've set your JRUBY_HOME? Try to execute the command from that directory instead, so you bin\jruby bin\camping blog.rb.

Of course you have to move the blog.rb-file too.

Good luck.

Unknown sa...

thanks, it works like a charm.
BTW, I think jruby can have a better way to manage classpath.
It is not a good idea to add all jars to system variable

Anonym sa...

You spilled coffee on my dry socks. ;(

Anonym sa...

WEBrick reports following error

!! trouble loading blog: [SyntaxError] blog.rb:0: Invalid char `\273' in expression

Anonym sa...

Best to copy and paste
http://opensource.ologix.com/blog.rb

Saving it imports invisible illegal characters.

Anonym sa...

Actually, it is my editor that seems to be the source.

Erik van Oosten sa...

http://redhanded.hobix.com/inspect/campingGetsAllJavayPlacingItAtTheVeryEpicenterOfHate.html

LOL

Anonym sa...

While using SVN rev. 2232 of JRuby, I get the following error:

$ jruby $JRUBY_HOME/bin/gem install camping --no-ri --no-rdoc --include-dependencies
Bulk updating Gem source index for: http://gems.rubyforge.org
ERROR: While executing gem ... (NoMethodError)
undefined method `rubygems_version' for 0.9.7:Gem::Version

Curiously, I can install Rails without problems using the same JRuby version. Can you tell us what revision of JRuby you used?

Ola Bini sa...

Ugo!

This is a general problem with JRuby (and MRI too, but not so often). It seems that sometimes the symbol tables get scrambled when working with lots of data. This used to happen on MacOS X in 1.8.1, and has been seen from and to in every version of JRuby fast enough to run RubyGems.

My solution when it happens is to just repeat the action. It usually works the next time around.

Good luck! I'm looking forward to your talk on RailsConfEU!

Anonym sa...

I tried it many times yesterday with no luck. Tried again once this morning and it worked.

Maybe I shouldn't have tried it when my MacBook was tired after a long day's work. ;)

ypt78 sa...

I am getting the following error
C:\rnd\my-ruby-projects>jruby %JRUBY_HOME%\bin\camping blog.rb
Exception in thread "main" java.lang.Error: unexpected type class org.jruby.ast.
CallNode at c:/rnd/jruby-trunk/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:[3
6,36]:[1017,1020]
at org.jruby.internal.runtime.methods.EvaluateCallable.procArityOf(Evalu
ateCallable.java:95)
at org.jruby.internal.runtime.methods.EvaluateCallable. init (EvaluateCa
llable.java:57)
at org.jruby.ast.IterNode.getCallable(IterNode.java:106)
at org.jruby.evaluator.EvaluateVisitor$IterNodeVisitor.execute(EvaluateV
isitor.java:1327)
at org.jruby.evaluator.EvaluationState.executeNext(EvaluationState.java:
274)
at org.jruby.evaluator.EvaluationState.begin(EvaluationState.java:320)
at org.jruby.internal.runtime.methods.EvaluateCallable.internalCall(Eval
uateCallable.java:67)
at org.jruby.internal.runtime.methods.AbstractCallable.call(AbstractCall
able.java:64)
at org.jruby.runtime.ThreadContext.yieldInternal(ThreadContext.java:496)

at org.jruby.runtime.ThreadContext.yieldCurrentBlock(ThreadContext.java:
436)
at org.jruby.evaluator.EvaluateVisitor$Yield2.execute(EvaluateVisitor.ja
va:2077)
at org.jruby.evaluator.EvaluationState.executeNext(EvaluationState.java:
274)
at org.jruby.evaluator.EvaluationState.begin(EvaluationState.java:320)
at org.jruby.RubyObject.eval(RubyObject.java:445)
at org.jruby.internal.runtime.methods.DefaultMethod.internalCall(Default
Method.java:111)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:367)
at org.jruby.RubyObject.callMethod(RubyObject.java:311)
at org.jruby.RubyObject.callInit(RubyObject.java:456)
at org.jruby.RubyClass.newInstance(RubyClass.java:174)
at sun.reflect.GeneratedMethodAccessor34.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.jruby.runtime.callback.ReflectionCallback.execute(ReflectionCallb
ack.java:140)
at org.jruby.internal.runtime.methods.CallbackMethod.internalCall(Callba
ckMethod.java:79)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:367)
at org.jruby.RubyObject.callMethod(RubyObject.java:319)
at org.jruby.evaluator.EvaluateVisitor$CallNodeVisitor.execute(EvaluateV
isitor.java:574)
at org.jruby.evaluator.EvaluationState.executeNext(EvaluationState.java:
274)
at org.jruby.evaluator.EvaluationState.begin(EvaluationState.java:320)
at org.jruby.evaluator.EvaluateVisitor$IterNodeVisitor.execute(EvaluateV
isitor.java:1332)
at org.jruby.evaluator.EvaluationState.executeNext(EvaluationState.java:
274)
at org.jruby.evaluator.EvaluationState.begin(EvaluationState.java:320)
at org.jruby.RubyObject.eval(RubyObject.java:445)
at org.jruby.Ruby.loadScript(Ruby.java:838)
at org.jruby.runtime.load.ExternalScript.load(ExternalScript.java:50)
at org.jruby.runtime.load.LoadService.load(LoadService.java:180)
at org.jruby.RubyKernel.load(RubyKernel.java:622)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.jruby.runtime.callback.ReflectionCallback.execute(ReflectionCallb
ack.java:140)
at org.jruby.internal.runtime.methods.CallbackMethod.internalCall(Callba
ckMethod.java:79)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:367)
at org.jruby.RubyObject.callMethod(RubyObject.java:319)
at org.jruby.evaluator.EvaluateVisitor$FCallNodeVisitor.execute(Evaluate
Visitor.java:1077)
at org.jruby.evaluator.EvaluationState.executeNext(EvaluationState.java:
274)
at org.jruby.evaluator.EvaluationState.begin(EvaluationState.java:320)
at org.jruby.Ruby.eval(Ruby.java:210)
at org.jruby.Main.runInterpreter(Main.java:176)
at org.jruby.Main.runInterpreter(Main.java:145)
at org.jruby.Main.run(Main.java:111)
at org.jruby.Main.main(Main.java:86)

ypt78 sa...

I still have the same above problem.Can somebody troubleshoot it?
Thx

Anonym sa...

I believe camping uses an unsupported syntax.

I changed

opts.on("-h", "--host HOSTNAME", "Host for web server to bind to (default is all IPs)") { |conf.host| }
opts.on("-p", "--port NUM", "Port for web server (defaults to #{conf.port})") { |conf.port| }
opts.on("-d", "--database FILE", "Database file (defaults to #{conf.db})") { |conf.db| }
opts.on("-l", "--log FILE", "Start a database log ('-' for STDOUT)") { |conf.log| }


to


opts.on("-h", "--host HOSTNAME", "Host for web server to bind to (default is all IPs)") { |h| conf.host= h }
opts.on("-p", "--port NUM", "Port for web server (defaults to #{conf.port})") { |p| conf.port = p }
opts.on("-d", "--database FILE", "Database file (defaults to #{conf.db})") { |d| conf.db = d }
opts.on("-l", "--log FILE", "Start a database log ('-' for STDOUT)") { |l| conf.log = l }


in camping.

ypt78 sa...

Do I need to make coding changes to camping?
even
jruby .\camping foo.rb
gives the same error.
I did a svn update of Jruby and did install camping again as explained in the tutorial. It doesn't help.

Anonym sa...

I got the follwing error when I try to run
gem install camping --no-ri --no-rdoc --include-dependencies


ERROR: Error installing gem camping[.gem]: No metadata found!

Anonym sa...

I did the changes in the camping file. In blog.rb, I only put "require 'rubygems'", but I got the following error:

!! trouble loading blog: not a Camping app, no Blog module found
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:91:in `method_m
issing': undefined method `abort' for main:Object (NoMethodError)
from c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:91

from ..\bin\camping:18:in `load'
from ..\bin\camping:18


Any idea of how to fix the problem?

Anonym sa...

require 'rubygems'
require 'camping'
require 'camping/session'

gives:

C:\fredWork\jruby\blog>jruby %JRUBY_HOME%\bin\camping blog.rb
!! trouble loading blog: [TypeError] undefined superclass 'Base'
c:/fredWork/jruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27
c:/fredWork/jruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27
c:/fredWork/jruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `requi
re'
c:/fredWork/jruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `requi
re'
C:/fredWork/jruby/blog/blog.rb:3
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/
dependencies.rb:140:in `load'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/activesupport-1.3.1/lib/active_support/
dependencies.rb:140:in `load'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/lib/camping/reloader.rb:61:
in `load_app'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/lib/camping/reloader.rb:40:
in `initialize'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:88:in `new'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:88
c:\fredWork\jruby\bin\camping:18:in `collect!'
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:89
c:\fredWork\jruby\bin\camping:18:in `load'
c:\fredWork\jruby\bin\camping:18
c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:91:in `method_m
issing': undefined method `abort' for Object:0x650892 @gempath_searcher=Gem:
:GemPathSearcher:0x17dc1cb @lib_dirs={2850225=>"c:/fredWork/jruby/lib/ruby/gems/
1.8/gems/builder-2.0.0/{lib,bin}", 25252664=>"c:/fredWork/jruby/lib/ruby/gems/1.
8/gems/markaby-0.5/{lib,bin}", 25068634=>"c:/fredWork/jruby/lib/ruby/gems/1.8/ge
...

bindir="bin", @email="why@ruby-lang.org", @required_ruby_version=Gem::Version:
:Requirement:0x15b0333 @version=nil, @requirements=[[">", #Gem::Version:0x23756
@version="0.0.0">]]>, @autorequire="metaid", @loaded=true, @date=Fri Feb 17 00:
00:00 CET 2006, @extra_rdoc_files=[], @authors=["why the lucky stiff"], @summary
="slight metaprogramming helpers", @default_executable=nil, @extensions=[]>, #G
em::Specification:0x12368df @homepage=nil, @name="sources", @executables=[], @ru
byforge_project=nil, @cert_chain=nil, @rubygems_version="0.9.0", @requirements=[
], @has_rdoc=false, @signing_key=nil, @rdoc_options=[], @loaded_from="c:/fredWor
k/jruby/lib/ruby/gems/1.8/specifications/sources-0.0.1.gemspec", @test_files=[],
@files=["lib/sources.rb"], @platform="ruby", @specification_version=1, @version
=Gem::Version:0x2f2295 @version="0.0.1">, @dependencies=[], @require_paths=["l
ib"], @post_install_message=nil, @bindir="bin", @email=nil, @required_ruby_versi
on=Gem::Version::Requirement:0x878c4c @version=nil, @requirements=[[">", Gem
::Version:0x23756 @version="0.0.0">]]>, @autorequire="sources", @loaded=false, @
date=Sat Jul 29 00:00:00 CEST 2006, @extra_rdoc_files=[], @authors=[], @summary=
"This package provides download sources for remote gem installation", @default_e
xecutable=nil, @extensions=[]>]>> (NoMethodError)
from c:/fredWork/jruby/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:91

from c:\fredWork\jruby\bin\camping:18:in `load'
from c:\fredWork\jruby\bin\camping:18

Anonym sa...

Exception in thread "main" java.lang.Error: unexpected type class org.jruby.ast.
CallNode at D:/jruby-0.9.1/lib/ruby/gems/1.8/gems/camping-1.5/bin/camping:[36,36
]:[1017,1020]
at org.jruby.internal.runtime.methods.EvaluateCallable.procArityOf(Evalu
ateCallable.java:85)
at org.jruby.internal.runtime.methods.EvaluateCallable.(EvaluateCa
llable.java:57)
at org.jruby.ast.IterNode.getCallable(IterNode.java:113)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:836)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:933)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:222)
at org.jruby.internal.runtime.methods.EvaluateCallable.internalCall(Eval
uateCallable.java:67)
at org.jruby.internal.runtime.methods.AbstractCallable.call(AbstractCall
able.java:64)
at org.jruby.runtime.ThreadContext.yieldInternal(ThreadContext.java:552)

at org.jruby.runtime.ThreadContext.yieldCurrentBlock(ThreadContext.java:
499)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:1365)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:810)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:933)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:222)
at org.jruby.RubyObject.eval(RubyObject.java:453)
at org.jruby.internal.runtime.methods.DefaultMethod.internalCall(Default
Method.java:112)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:379)
at org.jruby.RubyObject.callMethod(RubyObject.java:323)
at org.jruby.RubyObject.callInit(RubyObject.java:461)
at org.jruby.RubyClass.newInstance(RubyClass.java:174)
at sun.reflect.GeneratedMethodAccessor35.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.jruby.runtime.callback.ReflectionCallback.execute(ReflectionCallb
ack.java:140)
at org.jruby.internal.runtime.methods.CallbackMethod.internalCall(Callba
ckMethod.java:79)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:379)
at org.jruby.RubyObject.callMethod(RubyObject.java:331)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:305)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:841)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:490)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:933)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:222)
at org.jruby.RubyObject.eval(RubyObject.java:453)
at org.jruby.Ruby.loadScript(Ruby.java:863)
at org.jruby.runtime.load.ExternalScript.load(ExternalScript.java:50)
at org.jruby.runtime.load.LoadService.load(LoadService.java:180)
at org.jruby.RubyKernel.load(RubyKernel.java:637)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.
java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAcces
sorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:324)
at org.jruby.runtime.callback.ReflectionCallback.execute(ReflectionCallb
ack.java:140)
at org.jruby.internal.runtime.methods.CallbackMethod.internalCall(Callba
ckMethod.java:79)
at org.jruby.internal.runtime.methods.AbstractMethod.call(AbstractMethod
.java:58)
at org.jruby.RubyObject.callMethod(RubyObject.java:379)
at org.jruby.RubyObject.callMethod(RubyObject.java:331)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:679)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:933)
at org.jruby.evaluator.EvaluationState.eval(EvaluationState.java:222)
at org.jruby.Ruby.eval(Ruby.java:235)
at org.jruby.Main.runInterpreter(Main.java:177)
at org.jruby.Main.runInterpreter(Main.java:146)
at org.jruby.Main.run(Main.java:111)
at org.jruby.Main.main(Main.java:86)

Anonym sa...

Payday Loans No Hassle Payday Loans - Payday Loans and Payday Advance Loans online with no hassle

Expert sa...

Atomic SEO The Most Powerful Search Engine Marketing Company On The Web!

Anonym sa...

Atomic SEO I really like this blog it's very informative and a good source of information.

Anonym sa...

Atomic SEO I really like this blog when I first signed up here i wasn't sure were to post...

Anonym sa...

Atomic SEO I really like this blog when I first signed up here i wasn't sure were to post...

Anonym sa...

Atomic SEO I ended up at this blog on acident how can I ecome a full time member...

Anonym sa...

Joe Schroeder Nice blog. Check out my blog on the truth about making money from home

Anonym sa...

charlottesville virginia real estate Was reallt ammazing to look at X-mas time this year...

Anonym sa...

Atlantic City Tickets Show, Concert & Event ticket for all of Atlantic City!

Anonym sa...

For more information1. Betta fish have superbly developed eyesight and therefore they swim to the top of the tank whenever they see a human hand hovering over it to place the food in.2. Another name for betta fish is the Siamese fighting fish and its name is pronounced in the same way as when we say the Greek letter beta, it is for this reason that some misspell the name and is why you often see it written beta fish, which is the American way. Some think that the name has something to do with the Greek letter, but it is actually derived from the Thai word ikan bettah. Betta fish are known as pla-kad in Thailand and live in shallow freshwater. 3. Because of the fantastic colours of the male betta fish and other advantages, they are the most popular aquarium fish. This of course doesnt mean you can just put them in the tank and not look after them, they need a lot of looking after.4. Betta fish actually originate from the Southeast Asia, Thailand, Malaysia and China.5. They grow to about 3 inches and have a relatively short lifespan of about 2 years, but some live up to 4 years, it has been known that in well looked after aquariums, some have lived over 6 years.6. When the male betta fish is in his aggressive stage he will puff himself out. During this puffing out he raises his gill covers and fins to make himself look...For more information

Anonym sa...

You really have a nice blog setup here. I hope mine will be as good. Phone Books

Anonym sa...

I recently discovered a really good website on making money.If interested in making money then I suggest you look there.

Anonym sa...

Annuaire Adulte

Anonym sa...

Good post.
I was searching the internet for cheap internet phone service (Voip) and found a company called Via Talk. They are cheaper and just as good as Vonage.

They are now offering 1 year phone service Free when you purchase 1 year – for a limited time. Check it out at Via Talk

Anonym sa...

Nice blog.
If you’re interested in free weight loss tips please visit this site.

Unknown sa...

Hey you have a very good blog.

If by any chance you need weight loss help check out by blog: Weight Loss Tips

Anonym sa...

I recently visited a really informative website on making money.
If you are interested in making money then I suggest you look there.

Anonym sa...

I recently visited a really informative website on making money.
If you are interested in making money then I suggest you look there.

Anonym sa...

FREE Business Advertising Tips The Most Powerful Internet Classified Advertising Methods On The Web! "TOP" Rated Money Making Website! A Must See!!!

Anonym sa...

Hey cool blog. Have you used squidoo yet? I've been doing product review there.

Here are just a few of them:

Burn The Fat Feed The Muscle Review
Fat Loss 4 Idiots Review
AllPSPGames Review
PSP Blender Review
Satellite TV Elite Review
The Simple Golf Swing Review
Jamorama Review

Well hope you liked my reviews and you should check out squidoo sometime it is a lot easier than bloggin.

Anonym sa...

Hey cool blog. Have you used squidoo yet? I've been doing product review there.

Here are just a few of them:

Burn The Fat Feed The Muscle
Fat Loss 4 Idiots
All PSP Games
PSP Blender
Satellite TV Elite
The Simple Golf Swing
Jamorama

Well hope you liked my reviews and you should check out squidoo sometime it is a lot easier than bloggin.

Greg

Anonym sa...

What can we not do with blogs? Reading posts as these stir the creative force in me but what do you think would be the future of blogs with the arrival of web 2.0 platform? Stay-safe-on-the-net

Anonym sa...

Top Shopping , Health, & Entertainment Site 2 Spendless is another great find online that features key discounts and specials that the web has to offer. One of the health specials is about the American Consultants Rx discount prescription cards. It is "free". Don't forget to check out the job section as well if you are looking for a fresh employment start.This site has it all...

Anonym sa...

New interesting things to learn.Sure will be back soon.--------------------------- Herbal Remedies

Anonym sa...

For many years, it was thought that Irritable Bowel Syndrome may have been caused by stress. Today it is believed that, while stress may be a trigger for IBS, it may not be the cause. Rather, the symptoms may get worse when you are under stress: when you travel, attend social events or change your daily routine. Your symptoms may also get worse if you do not eat enough healthy foods or after you have eaten a big meal. Read more about how to clean your intestines and colon to help relieve IBS symptoms.

Anonym sa...

Are there any good sites on lodging tourism travel?I recently found one really informative website on lodging tourism travel .If you know of other good sites about lodging tourism travel , I would appreciate your thoughts and any additional URL you may wish to share on this page.

Anonym sa...

Hey I liked the blog.

I got a site on XoftSpySE Review which is a antispyware to protect your computer.

Hope you like my site also.

Anonym sa...

Dog Gifts Hi, found a great gift site for you, has all breeds and made in the USA.

Mantasha sa...

Great site buddy . Really I like ur site I think u r also a Java Developer like me any ways I have some reference books of java which are readable .... Following are the books

Programmer's Guide to Java
Certification A Comp. Primer SE
By Khalid A. Mughal, Rolf W. Rasmussen Publisher : Addison Wesley
Pub Date : August 04, 2003

Java™ Development on PDAs
Building Applications PocketPC
By Daryl Wilding-McBride Publisher : Addison Wesley
Pub Date : June 05, 2003


Learning Java™, 2nd Edition
Publisher : O'Reilly Pub Date : July 2002
Pub Date : June 05, 2003

Jython for Java Programmers
By Robert W. Bill Publisher : New Riders Publishing
Pub Date : December 21, 2001

Enterprise JavaBeans, 3rd Edition
By Richard Monson-Haefel Publisher : O'Reilly
Pub Date : September 2001

Java 1.5 Tiger: A Developer's Notebook
By David Flanagan, Brett McLaughlin Publisher : O'Reilly
Pub Date : June 2004


Java Tutorials Index 1
Java Data Objects
By David Jordan, Craig Russell Publisher : O'Reilly
Pub Date : April 2003

Java™ Extreme Programming Cookbook
By Eric M. Burke, Brian M. Coyner Publisher : O'Reilly
Pub Date : March 2003


Java™ Performance Tuning, 2nd Edition
By Jack Shirazi Publisher : O'Reilly
Pub Date : January 2003

Java™ Performance Tuning, 2nd Edition
By Jack Shirazi Publisher : O'Reilly
Pub Date : January 2003

JavaScript & DHTML Cookbook
By Danny Goodman Publisher : O'Reilly
Pub Date : April 2003

Java Servlet & JSP Cookbook
By Bruce W. Perry Publisher : O'Reilly
Pub Date : January 2004
Java Tutorials Index 2

Anonym sa...

This is a great Blog! Pardon if I landed on the wrong blog.
As a newbie I just can’t help sharing the following
url with you. It made al the difference to my financial
Situation. For only $7.00 you get your own
ATM Machine. Just follow the link below!

Anonym sa...

Lose Weight FAST with Pure Hoodia I enjoyed your blog. I look forward to more good things. http://www.yourweightlossclub.com/free-hoodia-pills.html

Anonym sa...

Free Discount Prescription Drug Cards Here is another
site that features no cost prescription cards that can be downloaded.

Anonym sa...

Prescription Cards Prescription assistance online for uninsured.

Anonym sa...

Prescription Cards Prescription assistance online for uninsured.

Anonym sa...

Web Promotion Software The Most Powerful Search Engine Marketing Software On The Web!

Anonym sa...

Hi,

Nice blog and layout.... Interested in link exchange?

Mystery,

Home Based Online Internet Businesses & Marketing - Earn Or Make Money Online - Google - Search Engines - Search Engine Marketing

Anonym sa...

Hi,

Nice blog and layout.... Interested in link exchange?

Mystery,

Home Based Online Internet Businesses & Marketing - Earn Or Make Money Online - Google - Search Engines - Search Engine Marketing

Anonym sa...

Hi,

Nice blog and layout.... Interested in link exchange?

Mystery,

Home Based Online Internet Businesses & Marketing - Earn Or Make Money Online - Google - Search Engines - Search Engine Marketing

Anonym sa...

Nice blog and layout! I was wondering if you do link exchange....

make money online
home based business
internet marketing
online advertising
work from home
better business bureau florida
search engine marketing
queerclick
online stock trading
marketingtool.com
real estate agent
brand
cnn money
msn money
credit cards
google
adwords au.weather.yahoo.com refsrc
oreillynet.com
affordable web hosting
yahoo
real estate agents
msn
myspace layouts
zagat survey
survey topsurveysite.com
online survey topsurveysite.com
kohls.com survey
pandaexpress.com survey
paid surveys
myspace surveys

Anonym sa...

Orange County SEO Companies The First time I called Atomic SEO, I was impressed with the level of expertise that I had percieved. They Got me Results and thats all I asked for so in Orange County and you need seo these are the guys to go too Orange County SEO Experts . Orange County SEO Experts as well... Orange County SEO services orange county website promoters

Anonym sa...

Woodland Park Real Estate The Most Powerful Search On The Web!

Anonym sa...

Online EBooks for Internet Marketing.

Anonym sa...

ghjgggh
test

Anonym sa...

Affordable SEO Set it and forget it, we do it all for you! Build links and build your web presence in the search engines.

Anonym sa...

Free Background Check I recently found this site. It's pretty good. You can sign up for free and conduct your own background check or people search.

Anonym sa...

Great blog. What an excellent discussion. #1 Home Based Business Opportunity

Anonym sa...

free Special website The Most Powerful website Marketing On The Web!

Anonym sa...

There are many ways to make money online. Going into niche markets:
I hope I helped you ;-)
You can sign up with this url :D http://bux.to/?r=bachhuy

Anonym sa...

Vietnam tour: http://www.footprintsvietnam.com

Anonym sa...

There are most definitely plenty of ways to make money online, but it all depends on how much effort you have in you in order to do what you gotta do!

-Kelly

Anonym sa...

Hey, you have nailed quite a few points in your story. By the way, have you used squidoo yet? I've been trying it out over there.

Here are some references:

Fat Loss 4 Idiots Diet Review
Fat Loss 4 Idiots

Well hope you liked my review and you should try out squidoo one day cos it is easier than using a blog.

Warm Regards,
Paul

Anonym sa...

Nice and informative post on this topic.

Anonym sa...

Your article is well written and provide important information on the specific topic.

Anonym sa...

I have no word to say that awsome... keep it up and Thanks alot