Monday, February 27, 2006

Lucene "Lock obtain timed out"

Today I had to interrupt my Java search server while it was indexing. Next time when I ran the server, it kept giving me the following messages:
caught a class java.io.IOException
with message: Lock obtain timed out: Lock@/tmp/lucene-bc34bb2d3ec966220086135e748049c5-write.lock

Solution was to delete the /tmp/lucene-* files.

Saturday, February 25, 2006

SQLException: Before start of result set

Today I was getting this error when making a SELECT query. This error is raised typically because you didn't call resultset.first() or resultset.next() function.

So before using a field from a record in MySQL


if ( Portal != null && Portal.next() ){
url = "//"+Portal.getString("portal_url")+".domain.com/"+ RS.getString("permalink");
}

Lucene Term Vectors

Using term vectors, we can provide choices to the users of our application that match the current choice. For instance, on a site that I am developing, I plan to use term vectors to offer my site users the ability to see "related articles" when they are reading an article.

Term Vectors are specified by setting an instance of Field.TermVector to true when calling a method similar to the following:

Field(String, String, Field.Store, Field.Index, Field.TermVector)



If you don't want to store the term vector

Field(name, value, Field.Store.YES, Field.Index.TOKENIZED)


If you do want to store the term vector

Field(name, value, Field.Store.YES, Field.Index.TOKENIZED, storeTermVector)



Term vector can also be stored for "Unstored" fields:
Don't store term vector, but tokenize and index the field, without storing the field

Field(name, value, Field.Store.NO, Field.Index.TOKENIZED)


If you do want to store term vector, tokenize and index the field, but not storing it in the index:

Field(name, value, Field.Store.NO, Field.Index.TOKENIZED, storeTermVector)




To find out whether term vector is stored for a field, we can use: isTermVectorStored

public final boolean isTermVectorStored()
IndexReader.getTermFreqVector(int,String)
IndexReader.getTermFreqVector(int, String)


Note from the manual about the above:
These methods do not provide access to the original content of the field, only to terms used to index it. If the original content must be preserved, use the stored attribute instead.


Other related functions:

isStoreOffsetWithTermVector


isStorePositionWithTermVector

Lucene Term Vectors

Using term vectors, we can provide choices to the users of our application that match the current choice. For instance, on a site that I am developing, I plan to use term vectors to offer my site users the ability to see "related articles" when they are reading an article.

Term Vectors are specified by setting an instance of Field.TermVector to true when calling a method similar to the following:

Field(String, String, Field.Store, Field.Index, Field.TermVector)



If you don't want to store the term vector

Field(name, value, Field.Store.YES, Field.Index.TOKENIZED)


If you do want to store the term vector

Field(name, value, Field.Store.YES, Field.Index.TOKENIZED, storeTermVector)



Term vector can also be stored for "Unstored" fields:
Don't store term vector, but tokenize and index the field, without storing the field

Field(name, value, Field.Store.NO, Field.Index.TOKENIZED)


If you do want to store term vector, tokenize and index the field, but not storing it in the index:

Field(name, value, Field.Store.NO, Field.Index.TOKENIZED, storeTermVector)




To find out whether term vector is stored for a field, we can use: isTermVectorStored

public final boolean isTermVectorStored()
IndexReader.getTermFreqVector(int,String)
// TermFreqVector myTermFreqVector = myreader.getTermFreqVector(id, "field_name");
IndexReader.getTermFreqVector(int, String)


Note from the manual about the above:
These methods do not provide access to the original content of the field, only to terms used to index it. If the original content must be preserved, use the stored attribute instead.


Other related functions:

isStoreOffsetWithTermVector


isStorePositionWithTermVector

Vectors in Java

Since I have been working on integrating Lucene, my current posts will talk more about Java than anything else.

What are Vectors?
Vectors defined in java.util package.

import java.util.Vector;


Think of them as arrays that dynamically resize.
Vectors are slower than arrays.

Sample usage

vec = new Vector();
v.addElement(myArticle);
for(int i=0;i<index;i++)
{
myArticle=(Article)vec.elementAt(i);
}

Lucene Field Types

The following are the field types in Lucene for my (and your) reference

Text: "tokenized and indexed, and is stored in the index" "Term vector will not be stored for this field"

Field.Store.YES, Field.Index.TOKENIZED
Field(name, value)
Field(name, value, storeTermVector)
Field(name, value, Field.Store.YES, Field.Index.TOKENIZED, storeTermVector)
Field(name, value, Field.Store.YES, Field.Index.TOKENIZED)


Unindexed: "not tokenized nor indexed, but is stored in the index"

Field.Index.NO
e.g. Field(name, value, Field.Store.YES, Field.Index.NO)


Unstored: "tokenized and indexed, but that is not stored in the index"

Field(name, value, Field.Store.NO, Field.Index.TOKENIZED)
and
Field(name, value, Field.Store.NO, Field.Index.TOKENIZED, storeTermVector)



Keyword: "not tokenized, but is indexed and stored"

Field(name, value, Field.Store.YES, Field.Index.UN_TOKENIZED)

org.apache.lucene.document.Field has been deprecated

I have been working on getting Lucene to work with Ruby on Rails. In my SearchServer.java, I kept getting the following messages on compile:


Note: SearchServer.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.
Note: SearchServer.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.



Then I executed:

javac SearchServer.java -Xlint:deprecation


and got the output:

SearchServer.java:297: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("title", RS.getString("title")));
^
SearchServer.java:298: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("type", "article"));
^
SearchServer.java:299: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("author", RS.getString("author")));
^
SearchServer.java:300: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("body", RS.getString("body")));
^
SearchServer.java:301: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("extended", RS.getString("extended")));
^
SearchServer.java:302: warning: [deprecation] Text(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated
doc.add(Field.Text("tags", RS.getString("tags")));

warning: [deprecation] UnIndexed(java.lang.String,java.lang.String) in org.apache.lucene.document.Field has been deprecated



After researching online I confirmed that the document.Field has infact been deprecated. What this means is that if you were to upgrade Lucene these functions may no longer be available.

The correct functions are mentioned in the Lucene demos that come with 1.9


/**
* Copyright 2004 The Apache Software Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

.................

public class FileDocument {
/** Makes a document for a File.
<p>
The document has three fields:
<ul>
<li><code>path</code>--containing the pathname of the file, as a stored,
untokenized field;
<li><code>modified</code>--containing the last modified date of the file as
a field as created by <a
href="lucene.document.DateTools.html">DateTools</a>; and
<li><code>contents</code>--containing the full contents of the file, as a
Reader field;
*/
public static Document Document(File f)
throws java.io.FileNotFoundException {

// make a new, empty document
Document doc = new Document();

// Add the path of the file as a field named "path". Use a field that is
// indexed (i.e. searchable), but don't tokenize the field into words.
doc.add(new Field("path", f.getPath(), Field.Store.YES, Field.Index.UN_TOKENIZED));

// Add the last modified date of the file a field named "modified". Use
// a field that is indexed (i.e. searchable), but don't tokenize the field
// into words.
doc.add(new Field("modified",
DateTools.timeToString(f.lastModified(), DateTools.Resolution.MINUTE),
Field.Store.YES, Field.Index.UN_TOKENIZED));

// Add the contents of the file to a field named "contents". Specify a Reader,
// so that the text of the file is tokenized and indexed, but not stored.
// Note that FileReader expects the file to be in the system's default encoding.
// If that's not the case searching for special characters will fail.
doc.add(new Field("contents", new FileReader(f)));

// return the document
return doc;
}

Friday, February 24, 2006

XMLRPC::FaultException

If you are getting this message, check your Ruby on Rails logs.

XMLRPC::FaultException (java.lang.Exception: RPC handler object not found for "test": No default handler registered):
/usr/local/lib/ruby/1.8/xmlrpc/client.rb:403:in `call'


Typically, it means that you do not have a default handler registered.

A default handler is registered using something like (Thanks Erik):


WebServer server = new WebServer(port);
server.addHandler("$default", new SearchServer
(FSDirectory.getDirectory(indexPath, false)));

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/codec/DecoderException

Do I hate Java or what? I have been trying to compile a searchServer program but was stuck on the following error for quite long:

Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/codec/DecoderException

I had placed the commons-codec-1.2.jar in my class path when compiling using -cp and was including the needed classes in my program via import.

The solution was to add the path to commons-codec-1.2.jar in my .bashrc file and then export it.

Frank

Thursday, February 23, 2006

Errno::ENOENT when opening URL

If you are trying to open a URL and are getting the error mesage:

Errno::ENOENT



Make sure you have this statement:

require 'open-uri'

Tuesday, February 21, 2006

HTTPS open-uri : basic authentication over SSL

Today I was looking for a way to open HTTPS urls with basic authentication. While looking, I came across this post wheich shows you how to patch open-uri to work with HTTPS.

I ended up using 'net/https' with 'net/http' to implement the basic authentication over SSL. So try it at your own risk and if it works, let me know ;)
Re: Patch that enables https in open-uri.rb

Here is the patch by Tanaka Akira

Index: lib/open-uri.rb
===================================================================
RCS file: /src/ruby/lib/open-uri.rb,v
retrieving revision 1.28
diff -u -p -r1.28 open-uri.rb
--- lib/open-uri.rb 5 Feb 2005 14:13:27 -0000 1.28
+++ lib/open-uri.rb 5 Feb 2005 14:14:25 -0000
@@ -539,9 +539,8 @@ module URI
header['host'] += ":#{uri.port}" if uri.port
end

- require 'net/http'
resp = nil
- Net::HTTP.start(self.host, self.port) {|http|
+ http_start(self.host, self.port) {|http|
http.request_get(uri.to_s, header) {|response|
resp = response
if options[:content_length_proc] && Net::HTTPSuccess === resp
@@ -576,11 +575,32 @@ module URI
end

include OpenURI::OpenRead
+
+ private
+ def http_start(host, port, &block)
+ require 'net/http'
+ Net::HTTP.start(host, port, &block)
+ end
end

class HTTPS
- def proxy_open(buf, uri, options) # :nodoc:
- raise ArgumentError, "open-uri doesn't support https."
+ private
+ def http_start(host, port, &block)
+ require 'net/https'
+ http = Net::HTTP.new(host, port)
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
+ setup_ca_certificates(http)
+ http.use_ssl = true
+ http.start(&block)
+ end
+
+ def setup_ca_certificates(http)
+ if File.file? '/etc/ssl/certs/ca-certificates.crt'
+ # Debian ca-certificates package
+ http.ca_file = '/etc/ssl/certs/ca-certificates.crt'
+ else
+ raise SecurityError, 'CA certificates not found'
+ end
end
end


For those interested in how exactly I did Basic Authentication over SSL:

require 'net/http'
require "net/https"
@http=Net::HTTP.new('www.site.com', 443)
@http.use_ssl = true
@http.start() {|http|
req = Net::HTTP::Get.new('/file')
req.basic_auth 'user', 'pass'
response = http.request(req)
print response.body
}


If you want to test the above out, try
Blogger API

Monday, February 20, 2006

Rails SOAP API XML-RPC

Ryan shows us how we can create a weblogupdates.ping XML-RPC client and server using Ruby on Rails.

Ryan's Scraps - Creating weblogUpdates.ping SOAP web services with Rails

Ruby Encrypt/Encode using digest/sha1

To encode strings:

Digest::SHA1.hexdigest "frankmash.blogspot.com"
=> "443072b4f12ca8796d901d5dce40924ef3c414fd"

Sunday, February 19, 2006

HABTM and attributes: Adding to self referential relations

One way you can add to a self referential relationship:
self.links << wp unless self.links.include?(wp)

and another way to add with attributes:

self.links.push_with_attributes(wp, { "link_text" => url[1] } ) unless self.links.include?(wp)

Multiple line regular expressions

Today I was looking for a way to remove javascript from HTML pages. The main problem was that regular expressions hardly take into account multiple lines. So after thinking quite a bit (and being unable to find a solution), I finally decided to do a little hack to solve my problem.

What was the hack? To convert all newline characters in HTML text to a temporary marker, apply the regular expression, and then convert them back.

originalresponse=originalresponse.gsub(/(\r\n|\n|\r)/,'[[[NEWLINE]]]')

originalresponse=originalresponse.gsub(/<script([^>]+)>.*?<\/script>/,'')

originalresponse=originalresponse.gsub('[[[NEWLINE]]]',"\n")

"No such file to load -- spark.rb"

If you are getting the following error:

"No such file to load -- spark.rb"

Make sure your app can read the spark.rb file in lib directory.

Frank

Saturday, February 18, 2006

Radio Shack Falls for Microsoft - Costs the Company 700 Stores?

The Seattle Times: Business : "DALLAS — RadioShack's troubles deepened Friday, as the electronics retailer announced it would close up to 10 percent of its 7,000 stores after a report of weak fourth-quarter earnings.

At an investment conference at RadioShack's Fort Worth, Texas, headquarters, where the news was released, President and Chief Executive David Edmondson apologized to investors over 'misstatements' on his résumé."


Yet another reason why Radio Shack chose Windows. Now that they can't afford it, they are closing 700 stores. Smart people: Makes me wonder who else lied on their resume like David Edmondson. And they thought they were saving millions by switching to Windows.

Friday, February 17, 2006

h :: html_escape

Ruby-Language

Today, I was looking for html_escape function and found it at the above URL:

197: def html_escape(s)
198: s.to_s.gsub(/&/n, '&').gsub(/\"/n, '"').gsub(/>/n, '>').gsub(/</n, '<')
199: end
200: alias h html_escape

Thursday, February 16, 2006

HABTM - Self Referential - Checking Whether An Association already exists

I am loving Rails. Today I found a cool way to check whether an association already exists in a habtm relationship

member1=Member.find(1)
member2=Member.find(2)

member1.friends << member2 unless member1.friends.include?(member2)


Pretty cool, eh?

Tuesday, February 14, 2006

Ruby Rails - Cool Routes.rb tip

Just found this cool routes.rb tip for custom routes:

Technoblog: Rails Routes - :controller/:action/:id VS. :controller/:id/:action: "Which allows me to change URLs like:http://somesite.com/person/show/123Into:http://somesite.com/person/123And:http://somesite.com/person/edit/123Into:http://somesite.com/person/123/edit"

Monday, February 13, 2006

Rails - Record Not Inserting - Record Not Updating

If your ActiveRecord model class is not inserting or updating objects, try making sure that the before_save function is not returning false.

-- Frank

acts_as_taggings : plugin vs gem

I asked the following question on the Ruby on Rails list:

I have gotten acts_as_taggable to work for a test application as documented on taggable.rubyforge.org

I am following Chad's excellent examples in ROR Recipes Beta book.

I have a few questions and hope that Chad or some other expert can help clarify them.

1. Why is the book suggesting to use taggings table instead of tags_contacts, as mentioned on taggable.rubyforge.org?
2. How can I use this taggings table instead of my tags_contacts?
3. How can I store additional fields such as member_id in the tags_contacts table or taggings table?
4. When I follow Chad's example on my Favorite class, (with tags_favorites and taggings table created) I keep getting the NoMethodError for "tag_with"? At first I thought this may be because I am using taggings table but then when I created tags_favorites table the error persisted.
5. I finally used the tag method as described on rubyforge.org and it works. So my question is, is tag_with a typo in the book or am I missing something?
6. Why does the following output say @new_record=false even when the record was created? Is it to prevent further entry of the same record?


The following are the responses by list members:

Michael Yacavone said:

The acts_as_taggable in the ROR Recipes book uses the _plugin_ written by DHH, not the _gem_ on rubyforge. Unfortunately, the plugin is not documented outside the ROR Recipes book right now.

Until DHH documents the plugin we're going to have dozens or hundreds of messages of confusion like this. Even then, the namespace collision will provide months of amusement.


To my surprise, David Heinemeier Hansson (Yes, DHH) replied:

Now here's the truly wonderful part of open source: Creator, maintainer, and documentor needs not all be caps on the same schmoe. Thus, if you find something useful, say a plugin like acts_as_taggable with the creator stamp DHH, you can choose to express your gratitude through one of the other caps.

That may be a patch to extend or it may be a tutorial about its usage or even READMEs and RDocs for the source. So try not to assume that creator equals sole maintainer or documentor. Only try to understand the truth: There is no vendor.


Michael replied back saying

I am actually trying to get a grip on this to help, which happens to be challenging for my skill level.

But when the top dog of Rails creates a namespace clash with a well-known gem, it sure would be nice to have some leadership and not defer to the magic of open-source community to sort it out eventually. Better to just say, "We're up against a new product release, and will get to this someday soon," or somesuch, which is probably pretty close to the truth.

Thought experiment: If someone else had released a plugin with the same name as a well-known gem, would the community have accepted it, or would they encourage the creator to change the name to avoid confusion? I assert that 1) the creator would have been encouraged to change the name, and 2) it is because DHH is the creator this didn't happen.

Of course, let a thousand flowers bloom, just don't expect this question to go away anytime soon.


And then


I have written a weblog entry which hopefully clarifies the situation as I understand it, and will eventually hit the Google index for future queries.
http://www.notio.com/2006/02/rails_acts_as_t.html


David then replied:


>" But when the top dog of Rails creates a namespace clash with a well-known gem, it sure would be nice to have some leadership and not defer to the magic of open-source community to sort it out eventually.

Namespace clashes are problematic for packages that wants to be used concurrently, not for alternatives. How would the situation have improved if I had called it acts_as_polymorphically_taggable (except for making jokes about such a terrible, terrible name)? Would we not have had the same questions as to what does what and why? Of course we would.

In any case, let me summarize the difference quickly. The main difference is not plugin vs gem, but rather that my version of acts_as_taggable is designed to work with multiple classes. So a single tag can be used in the tagging of multiple classes. This requires polymorphic associations and join models, which is why it only works Edge Rails (and will work in the forthcoming 1.1).

So if you need that feature, tagging of multiple classes with the same tags, then use my version. Otherwise, the old version is probably better. It's certainly more feature-rich and better documented.


My thanks to David and Michael for their clarifications.

Frank

Sunday, February 12, 2006

Tag expected, got String

If you are getting this error and you are using acts_as_taggable, the solution is to add/edit the following in _form.rhtml or related file:


< input id="tags" name="tags" size="30" type="text" value="" />

ActiveRecord::Migration

Documentation on how to use ActiveRecord::Migration

Saturday, February 11, 2006

Rails Interesting Posts

Some very interesting posts on Planet Rails that caught my eye.


P.S. I have submitted a request for this blog to be added and am patiently waiting :).

EdgeRails in Ruby on Rails

I was looking for a way to move to EdgeRails. A commenter on Ruby on Rails provided me with the following link:

EdgeRails in Ruby on Rails: "Edge Rails is a term that means you are running a local copy of the latest SVN trunk of Rails, instead of using gems. By checking out the trunk into your /vendor dir, Rails will automatically use that instead of the gems installed on your machine."


The above page tells you all about how you can move to EdgeRails whether you are using SVN or gems
--Frank

Thursday, February 09, 2006

String in Ruby:Strings Manual

Lots of Ruby String Goodies:

"A String object holds and manipulates an arbitrary sequence of bytes, typically representing characters. String objects may be created using String::new or as literals (see page 204).Because of aliasing issues, users of strings should be aware of the methods that modify the contents of a String object. Typically, methods with names ending in ``!'' modify their receiver, while those without a ``!'' return a new String. However, there are exceptions, such as String"


mixins: <, <=, ==, >=, >, between?
class methods: new String.new( aString ) -> aNewString
instance methods: % str % arg -> aString
* str * anInteger -> aString
+ str + aString -> aNewString
<<
str
<< aFixnum -> str
str << anObject -> str
<=> str <=> aString -> -1, 0, +1
== str == anObject -> true or false

and more

Wednesday, February 08, 2006

Rails acts_as_threaded plugin

Bob Silva has a wonderful plugin complete with screencast on his web site. If you are looking to add forum capability to your site, his plugin acts_as_threaded will work great. I implemented it in my application in less than 30 minutes.

Here is a quote from Bob Silva:
6 short of a dozen: "I've included an 11 minute screencast of building an ugly looking Threaded Forum, but the concepts are there. I can still remember spending many man hours with my programming team at Bravenet to design and build the forum product we offered there. If Rails existed back then, we could have built it in a day. (Leave me a comment on how the screencast turned out.)"


Make sure you don't name you model "Thread" as "thread" is a reserved word in ROR.

Frank

Tuesday, February 07, 2006

Rails Paginate DESC and notes

One way to specify the order:

@article_pages, @articles = paginate :articles, :order_by=> "created_at DESC", :per_page => 10


Reverse the list

for post in posts.reverse



Take everything within

for article in articles

and put it in _article.rhtml. Then replace the remaining "for" block with this line:

<%= render :partial => "article", :collection => @articles %>

We can place the above line in show.rhtml or whereever we like.

Sunday, February 05, 2006

Using partial in views and controllers

In views/unajax/index.rhtml

<ul id="items"><br /><%= render(:partial => 'item', :collection => @items) %><br /></ul><br />

In controllers/unajax_controller.rb
<br />def<br />item = Item.new(params[:item_body])<br />render(:partial => "item", :object => item, :layout => false)<br />end<br />
And in views/unajax/_item.rhtml

<li><br /><p><br /><%= item.posted_on.strftime("%H:%M:%S") %>:<br /><%= h(item.body) %><br /></p><br /></li><br />
Notes from Agile devel book

Rails: Commuincating between action level layout and global layout

Today I learned a cool new trick.

Lets say you have a controller named blog_controller.rb that uses "global" layout named default:

layout 'default'


And you have a views/blog/index.rhtml template (action level layout)
then you can communicate between the two using "content_for" variables:

For example, I can create a variable called @content_for_my_javascripts in views/blog/index.rhtml using

<% content_for("my_javascripts") do -%>
# javascript code here
<% end -%>


Now I can place the following in my global template (views/layout/default.rhtml) to put these javascripts there

<script type="text/javascript"><%= @content_for_my_javascripts %></script>


It seems that "action level layout templates" are rendered before the "global layout templates"

Cool eh

Rails - Paginate a collection or an array

Yesterday, I was looking for a way to paginate my collection. While searching online I found this wonderful snippet by Canadaduane that does the job very neatly.



def paginate_collection(collection, options = {})
default_options = {:per_page => 10, :page => 1}
options = default_options.merge options

pages = Paginator.new self, collection.size, options[:per_page], options[:page]
first = pages.current.offset
last = [first + options[:per_page], collection.size].min
slice = collection[first...last]
return [pages, slice]
end



The above snippet can be invoked using

@pages, @users = paginate_collection User.find_custom_query, :page => @params[:page]



Access the original post: Paginate an already-fetched result set (i.e. collection or array)
Also check out snippet by toolmantim on the above URL for block.

Rails MySQL Enum Patch

Andrey Tarantsov posted a patch on the Ruby on Rails list that can help you with adding Enum functionality to Rails. Enum functionality is not added by default.

Hello!

I've modified ActiveRecord a bit to support enum columns. This is
tested
only with MySQL, and maybe needs some changes for other adapters (if
they use another syntax or quoting style).

I believe this patch should be tested well, so I haven't (yet) tried to
post it to the official Trac. It is also not solid-as-a-rock when it
comes to quoting ENUM values.

After applying this thing, rake db_schema_dump properly handles enum
columns, and also it's possible to write migrations like this:

add_column :users, :level, :enum, :limit => ['viewer', 'author',
'admin']

Observe that I'm (over)using the limit field to specify ENUM values.

Here's the patch.


Index:
activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
===================================================================
---
activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
(revision 3486)
+++
activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
(working copy)
@@ -235,7 +235,15 @@

def type_to_sql(type, limit = nil) #:nodoc:
native = native_database_types[type]
- limit ||= native[:limit]
+ # this is a special case, because data type differs
+ case type
+ when :enum
+ fail ":enum column limit must be specified" if limit.nil?
+ fail ":enum column limit must be an array" unless
limit.is_a? Array
+ limit = limit.collect {|n| "'#{n}'"}.join ","
+ else
+ limit ||= native[:limit]
+ end
column_type_sql = native[:name]
column_type_sql << "(#{limit})" if limit
column_type_sql
Index:
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
===================================================================
---
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
(revision 3486)
+++
activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb
(working copy)
@@ -18,7 +18,7 @@
@sql_type = sql_type
# have to do this one separately because type_cast depends on
#type
@default = type_cast(default)
- @limit = extract_limit(sql_type) unless sql_type.nil?
+ @limit = extract_limit_or_enum_values(sql_type) unless
sql_type.nil?
@primary = nil
@text = [:string, :text].include? @type
@number = [:float, :integer].include? @type
@@ -44,6 +44,7 @@
when :text, :string then String
when :binary then String
when :boolean then Object
+ when :enum then Symbol
end
end

@@ -61,6 +62,7 @@
when :date then self.class.string_to_date(value)
when :binary then self.class.binary_to_string(value)
when :boolean then self.class.value_to_boolean(value)
+ when :enum then value.intern
else value
end
end
@@ -77,6 +79,7 @@
when :date then
"#{self.class.name}.string_to_date(#{var_name})"
when :binary then
"#{self.class.name}.binary_to_string(#{var_name})"
when :boolean then
"#{self.class.name}.value_to_boolean(#{var_name})"
+ when :enum then "#{var_name}.to_s.intern"
else nil
end
end
@@ -135,6 +138,26 @@
$1.to_i if sql_type =~ /\((.*)\)/
end

+ def extract_enum_values(sql_type)
+ paren_string = $1 if sql_type =~ /\((.*)\)/
+ return [] if paren_string.nil?
+ values = []
+ paren_string.split(",").each do |item|
+ item.strip!
+ item = item[1..-2] if item[0] == ?' && item[-1] == ?' #
remove quoting
+ values << item
+ end
+ values
+ end
+
+ def extract_limit_or_enum_values(sql_type)
+ if type == :enum
+ extract_enum_values(sql_type)
+ else
+ extract_limit(sql_type)
+ end
+ end
+
def simplified_type(field_type)
case field_type
when /int/i
@@ -157,6 +180,8 @@
:string
when /boolean/i
:boolean
+ when /enum/i
+ :enum
end
end
end
Index:
activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
===================================================================
--- activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
(revision 3486)
+++ activerecord/lib/active_record/connection_adapters/mysql_adapter.rb
(working copy)
@@ -107,7 +107,8 @@
:time => { :name => "time" },
:date => { :name => "date" },
:binary => { :name => "blob" },
- :boolean => { :name => "tinyint", :limit => 1 }
+ :boolean => { :name => "tinyint", :limit => 1 },
+ :enum => { :name => "enum", :limit => [] }
}
end


--

Saturday, February 04, 2006

Rails - Generating Documentation

To generate documentation for your application, simply run

rake appdoc


from within your app's directory.

Render Component

I was trying to put my login form on the home page and wanted to document the steps:

Put

<%= render_component :controller => "member", :action => "login", :layout => false, :params => { "member[login]" => "" } %>


in show.rhtml and have the layout turned off.

render (:layout=>false)


The above will not show any layout if placed in a function.

Also see:

# Renders the template for the action "goal" within the current controller
render :action => "goal"

# Renders the template for the action "short_goal" within the current controller,
# but without the current active layout
render :action => "short_goal", :layout => false

# Renders the template for the action "long_goal" within the current controller,
# but with a custom layout
render :action => "long_goal", :layout => "spectacular"

from ActionController/Base
Render component infinite loop

eXTReMe Tracker