把已有项目整合beast,共用beast里的users表,舒服啊。

推荐大家看这篇两篇文章,http://programmerassist.com/article/302http://blog.aisleten.com/2007/07/04/integrating-beast-forum-into-a-ruby-on-rails-app-part-1/

今天要下班回家了,明天仔细研究下发布出来。

 

At some point, it’s almost guaranteed that you will want a forum for your website. Forums make a great way to allow your users to contact you and each other, and their persistence allows for archiving of the answers so you (hopefully) don’t have to keep answering the same questions over and over. Moreover, most users are familiar with the forum concept and interface, thereby reducing the barrier to communication.

If all you want is to get a forum up as fast as possible, there are several free/open-source/commercial solutions. They will get you up and running very quickly, provide all the functionality you need, plus tons of features you’ll probably never use. If that’s what you want, stop reading and go get one.

Assuming you’re still here, then you’re probably in the same boat as me. You’ve got a pre-existing Ruby on Rails application, and you want to add a forum. Moreover, you want to add an integrated forum that allows your users to utilize the same login credentials as they use for your current app.

Enter the Beast! Beast is a pure RoR forum application that has everything you might expect from a forum, plus offers the glimmer of hope that you’ll be able to integrate it with your current app. Still, once you dive in, the glimmer starts looking pretty dim. I approached it several times and backed off before I finally had the confidence to finish it. Luckily for others, I made a lot of mistakes and am happy to share them.

The Goal

For now, my only goal is to unify usernames and passwords, preventing the users from having to register twice. Very important to note is the fact that I am NOT talking about a single sign-on. I am comfortable (at this point) with forcing the user to sign in twice. I simply want to make sure that the username and password are the same both times. Remember: KISS and baby steps.

Before you start

Before I describe the actual approaches, I would like to give some general advice on how to proceed, regardless of your approach. These following tips could potentially save you a lot of time in the very real case that something goes horribly wrong.

Create a development branch

You are about to attempt a potentially nasty bit of integration. You might need to hack your code (and Beast’s code) in very ugly ways. You may need to compromise your whole design. Worst of all, you might fail! At the end, you may realize that it just is not worth it, and you’re destabilizing your codebase too much to justify the payoff. Wouldn’t it be nice to simply write off the time and get back to work?

From another angle, perhaps you’re in the middle of the integration when a user finds a nasty bug on your live site. You need to fix the bug right now, but your codebase is in a totally unstable state. You can’t fix the bug without checking in your Beast changes, and they will break everything. Sounds like it’s going to be a long night…

If you do your work on a development branch, then switching back to the trunk is very easy. Simply check in all your changes on the branch, then wipe out your working copy and check out a fresh one from the trunk. All the work you did on Beast is still there (in the branch), but you’re working on the trunk, totally clean. You can continue main development (such as fixing nasty bugs), and get back to Beast when time allows. Finally, when you’re satisfied that Beast is ready, a simple merge-to-trunk is all it takes to bring all your changes into the trunk.

Bottom line: Do all your Beast integration work on a branch.

Grab Beast as a vendor branch

Beast is still under active development, and they just hit 1.0 Do you plan to stay current with it as it matures over time? I definitely plan to. However, integrating it with your application will almost definitely require you to modify it in some way. Heck, just installing it by itself will probably require modifications in order to get the text to say what you want.

Starting from a vendor branch will allow you to (hopefully) easily upgrade to newer versions, while keeping all the changes you had to make in order to get it working with your app.

We have already covered how to do a vendor branch and how to upgrade using a vendor branch, so go browse those posts if you don’t know what I’m talking about.

Please, I’m begging you, trust me on this one. Do the vendor branch and save yourself a huge headache when Beast 1.1 comes out…

The Approaches

I attacked the integration problem from three different angles. Two worked and one was a total failure. I’ll describe each one and its pros and cons. If I get anything wrong, or left something out, please do not hesitate to correct me in the comments. I really want this to be a valuable resource for the Rails community.

One commonality to note between all the approaches was that I never tried to fully integrate the applications from the perspective of controllers and routing. They each retained their own directory structure, they each will have their own root domain, and they will each require their own mongrel server, unless I can figure out how to get a single mongrel to serve both.

1. Separate databases; Users pulled from my app’s database

With this approach, I tried to keep separate the two databases, except for the Users. The RDocs for ActiveRecord::Base give instructions on how to force Rails to connect to a different database for a specific model. This seemed like an excellent way to handle the Beast integration.

I added all the missing Beast user columns to my Users table, created the secondary database, and updated the Beast User model to connect to my database. At first it worked, and I was pretty excited. However, as I navigated around the forums, it crashed almost immediately. Because there are several relationships between the User and other models, the system was attempting to run some joins. Perhaps other RDBMS’s can do cross-database joins, but it seems that MySQL cannot, at least not how I have it set up.

Bottom line: Didn’t work because MySQL freaked out about the cross-database joins.

2. Same databases; Beast tables renamed with beast_

My second attempt involved adding all the Beast tables to my current database, and then renaming all the Beast tables with a “beast_” prefix. It wasn’t strictly necessary as there were no naming conflicts, but I thought it would help with readability.

I left the users table and sessions tables untouched, as I already had a users table and simply added the Beast columns to it. The sessions table seemed like something special, so I decided not to mess with it.

To accomplish the renaming, all I had to do (I thought) was edit the migrations a little, then use the set_table_name directive in each of the model classes. When this was done, things looked like they would work.

What I didn’t realize was that much of the Beast database querying is done via specifying :conditions on a find() call. I didn’t look too closely, but perhaps the Beast team found they could not be served adequately by the find_by_x_and_y finders. The end result of this was that there were several places where the table names were listed explicitly inside the query conditionals. I ran several search/replace runs for “posts”, “forums”, and so on for all the Beast tables. Of course, these strings got several hundred hits throughout the codebase, and the replacement was a real chore.

Eventually I got Beast working, but I did not have a lot of confidence in the changes. I was not sure if I had caught everything, although this could be reasonably tested using the unit and functional tests. Most troubling to me, however, was the prospect of trying to integrate a new release. I had the sneaking suspicion that every time there was a new release of Beast, I would be hunting down table names inside of conditionals and replacing them by hand. Besides the tediousness, this was bound to produce bugs that I wouldn’t catch and would escape into production.

Bottom line: Works, but you’re in for a world of hurt when you want to upgrade to a new release of Beast.

3. Same database; Beast tables unmodified

As I mentioned earlier, I had no table name conflicts with Beast, so this was a viable option for me. Just add all the new Beast tables, and modify my users table to add the necessary fields.

This was a no-brainer, and took almost no time at all to accomplish. Run the migrations, make a few modifications to the authentication system, and you’re up and running. After trying the other two methods, I was able to do this one in about 5 minutes.

This is ultimately the approach I would recommend to anyone attempting the integration. Of course, it works best if you have no table naming conflicts. However, assuming that you do, I recommend only renaming the Beast tables that actually conflict. This will minimize the amount of find/replace hunting that needs to be done, both now and for future upgrades. Remember: KISS.

Bottom line: Works, is easy to do, and represents the least amount of risk when upgrading to new releases of Beast. This is my recommended solution.

Modifications to support acts_as_authenticated

Handling the database will be the hardest part, but if you are using acts_as_authenticated, you’ll need to make a few updates to the authentication that goes on.

It actually turned out to be very easy, but remember, this does not get you single sign-on, just unified usernames and passwords.

Modify the Beast User.rb, and remove or comment out the self.authenticate method. We will be replacing this. I think there might be a way to re-alias it, but deleting works just as well.

Replace the one you removed with the following method:

  1. def self.authenticate(login, password, activated=true)   
  2.   u = find_by_login(login)   
  3.   return nil unless u   
  4.      
  5.   crypted_pass = Digest::SHA1.hexdigest("--#{u.salt}--#{password}--")   
  6.        
  7.   return crypted_pass == u.crypted_password ? u : nil  
  8. end  

def self.authenticate(login, password, activated=true) u = find_by_login(login) return nil unless u crypted_pass = Digest::SHA1.hexdigest("--#{u.salt}--#{password}--") return crypted_pass == u.crypted_password ? u : nil end

As you can see, this simply finds the User by their login, and checks their password the same way that acts_as_authenticated does.

Other than that, I modified reset_login_key! to change “password_hash” to “cypted_password” To be honest, I have no idea what this does, but the two fields are basically equivalent in the two authentication schemes (I think). Anyways, it seems to work.

Next steps

So why is this titled part 1? Because I want single sign-on, of course! The eventual goal is to unify the sign in interface so that the user signs in at one place and can then navigate through the entire site, including the forums, and never have to re-authenticate.

Part 2 (if it ever gets written) will deal with unifying the sign-on. This has been done before (see the references at the end of the post), but I’m just not ready to attempt it yet.

Update 2007-12-17 Adam from the comments has posted a great tip on how to easily get single sign-on. He says:

Single sign on is very easy. ALl you have to do is set your session domain to be the same by typing (for rails 2.0)

config.action_controller.session = { :session_key => “_session_id”, :secret => “secret_session” , :session_domain => “.mydomain.com” }

So now forums.mydomain.com and www.mydomain.com will use same cookie/session and you can host beast on a subdomain as a separate app. I then added a before_filter to application.rb that ensured session[:user] was populated otherwise i redirected to www.mydomain.com/login

I have not tried this yet, but telling the two Rails apps to use the same session is probably the best way to go.

Additional References

Beast Forums - A great place to ask questions about Beast…go figure ;)
Integrating Beast with another application - Someone else who did the same thing, but duplicated the User information across 2 databases

Call for participation

If I have gotten anything wrong, or you know a better way to do it, please post a note in the comments! This integration was fairly hard to do, and I made a lot of false starts. I want to make it smoother for those that follow.

Good luck, and don’t forget to make a development branch!

评论
carlosbdw 2007-12-26   回复
还没发出来阿~
发表评论

您还没有登录,请登录后发表评论

weskycn
搜索本博客
博客分类
最近加入圈子
存档
最新评论