Skip to main content

Rails and JPA (Instead of ActiveRecord)

Posted by bleonard on September 26, 2007 at 8:30 AM PDT

Out of sheer morbid curiosity, I wondered what it would take to replace ActiveRecord with JPA in the classic blog demo. Jeroen Zwartepoorte, a developer I met last week at RailsConf, convinced me to go ahead and publish my results.

In almost all cases, I tried to keep the view and controller code similar to what you would expect from generated scaffolding, so all of the JPA code is isolated in the model class. I also refrained from making any changes to the Java entity classes, but certainly that's also a route worth exploring.

Getting Started

  • Download and install NetBeans 6.0 Beta 1. Grab the Full distribution so you can get the Java IDE, Ruby and GlassFish.

Creating the Java Entity Classes

For consistency, I'm going to create the same blog application that's know to every Rails developer. However, instead of generating a model and running migrations, we'll create an Entity class and a persistence unit.

  1. Create a new Java Application named Post. There's no need for a Main Class.
  2. Create a new Persistence Unit. Select jdbc:derby://localhost:1527/sample [app on App] as the database connection and click Finish. Switch the the XML view and set the toplink.jdbc.password property to app.
  3. Create a new Entity Class named Posts. Set the package name to entity.
  4. Add fields and accessors for title and body. Your completed class should look as follows:



    /*
    * Posts.java
    *
    * Created on Sep 26, 2007, 10:15:53 AM
    *
    * To change this template, choose Tools | Templates
    * and open the template in the editor.
    */

    package entity;

    import java.io.Serializable;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;

    /**
    *
    * @author bleonard
    */
    @Entity
    public class Posts implements Serializable {
        private static final long serialVersionUID = 1L;
        private Long id;
        private String title;
        private String body;

        public void setId(Long id) {
            this.id = id;
        }

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        public Long getId() {
            return id;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }

    }

Test the Entity Classes

  1. Create a new JUnit Test named PostsTest in the entity package. You can uncheck the Test Initializer and Finalizer, we will not be using them.
  2. Add code to initialize the Entity Manager and test the Post entity. My complete PostTest class looks as follows:



    /*
    * PostsTest.java
    * JUnit 4.x based test
    *
    * Created on September 24, 2007, 7:42 PM
    */

    package entity;

    import javax.persistence.EntityManager;
    import javax.persistence.EntityManagerFactory;
    import javax.persistence.EntityTransaction;
    import javax.persistence.Persistence;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import static org.junit.Assert.*;

    /**
    *
    * @author bleonard
    */
    public class PostsTest {

        private static EntityManagerFactory emf = Persistence.createEntityManagerFactory("PostPU");
        private static EntityManager em = emf.createEntityManager();
        private static EntityTransaction trans = em.getTransaction();

        public PostTest() {
        }

        @Before
        public void setUp() throws Exception {
        }

        @After
        public void tearDown() throws Exception {
        }
       
        @Test
        public void testPersist() {
           
            // Persist some data...
            trans.begin();
            Posts post = new Posts();
            post.setId(new Long(100));
            post.setTitle("George");
            post.setBody("Jetson");
            em.persist(post);
            trans.commit();
           
            // Retrieve the data...
            Posts result = em.find(Posts.class, new Long(100));
            assertEquals(post, result);

            //Clean up for the next test...
            trans.begin();
            em.remove(post);
            trans.commit();
        }


  3. Add the Java DB Driver Library to the project's Test Libraries.
  4. Start the Database (Tools > Java DB Database > Start Server)
  5. Press Shift+F6 to run the test:



Create the Rails Application

Now that we appear to have a functioning entity class, let's create the Rails application that will use that class.

  1. Create a new Ruby on Rails application named jpa_blog.
  2. Generate a controller named blog with views list, new, show and edit. We'll develop each of these views in turn.
  3. In the Models folder, create a new Ruby class named Post (note, we are not generating a Post model).

Step 1: Listing the Entries

Code the View

  1. Replace the contents of list.rhtml with the following:


      <h1>The Ruby Blog</h1>
      <% @posts.reverse.each do |post| %>
        <h2><%= post.title %></h2>
        <p><%= post.body %></p>
        <small> <%= link_to 'Permalink', :action => 'show', :id => post %></small>
        <hr>
      <% end %><br />
      <%= link_to 'New post', :action => 'new' %>
     

Code the Controller

  1. Add the following to blog_controller.rb:


      def index
        list
        render :action => 'list'
      end

      def list
        # Declare a paginator for the posts table
        @post_pages, @posts = paginate :posts, :per_page => 10
      end   

Code the Model

  1. Add the following to the top of post.rb:


    require 'java'

    include_class 'javax.persistence.Persistence'
    include_class 'javax.persistence.EntityManager'
    include_class 'javax.persistence.EntityManagerFactory'
    include_class 'java.util.List'
    include_class 'java.lang.Long'
    include_class 'entity.Posts' 
  2. Add the accessors:


    attr_accessor :id, :title, :body
  3. Define private methods for getting the entity manager and finding all rows in the table:


     
      private
          
      def self.getEntityManager
        emf = Persistence.createEntityManagerFactory("PostPU")
        return emf.createEntityManager()
      end

      def self.find_all
        query = getEntityManager.createQuery("SELECT p FROM Posts p")
        list = query.getResultList()
         
        posts = []  #Create a new array to return
         
        list.each {|post|
          temp = Post.new
          temp.id = post.getId
          temp.title = post.getTitle
          temp.body = post.getBody
          posts << temp
        }
        return posts
      end 
         
  4. Define the methods that will be called by the paginate method used in the controller (these are public):


      # Called by `paginate'
      def self.count(*args)
        query = getEntityManager.createQuery("SELECT p FROM Posts p")
        count = query.getResultList().size();
        puts count
      end

      def self.find(*args)
        case args.first
        when :all   then find_all
        else             find_from_id(args.first)
        end
      end
     

Configure JRuby

  1. Open the Options dialog to find the location of your JRuby interpreter.
  2. Copy the following jars to your JRuby lib directory:
    • Post.jar (build your project if you don't have a Post.jar)
    • derbyclient.jar (Tools > Java DB Database > Settings will give you the location of derbyclient.jar)
    • toplink-essentials.jar and toplink-essentials-agena.jar (check the Library Manager for the location of TopLink Essentials)

Test the List

This will not be very exciting because the list is empty (you can tweak the unit test to add some entries). But we will verify the application runs without exceptions. With your cursor in the index action, press F6 to start WEBrick and launch the browser. Browse to http://localhost:3000/blog.



Step 2: Creating a New Entry

Code the View

  1. Create a new partial, _form.rhtml, to be used by both the new and edit pages:


      <p>Title<br/>
      <%= text_field 'post', 'title'  %></p>

      <p>Body<br/>
      <%= text_area 'post', 'body'  %></p>
  2. Replace the contents of new.rhtml with the following:


      <h1>New post</h1>
     
      <% form_tag :action => 'create' do %>
         <%= render :partial => 'form' %>
         <%= submit_tag "Create" %>
       <% end %>
      
       <%= link_to 'Back', :action => 'list' %>

Code the Controller

  1. Add the following to blog_controller.rb:


  2.   def create
        @post = Post.new(params[:post])
        if @post.save
          flash[:notice] = 'Post was successfully created.'
          redirect_to :action => 'list'
        else
          render :action => 'new'
        end
      end 
     

Code the Model

  1. Define the initialize and save methods in post.rb:


      def initialize(attributes = nil)
        if !attributes.nil?
          @title = attributes[:title]
          @body = attributes[:body]
        end
      end   
     
      def save
        p = Posts.new
        p.title = title
        p.body = body
        em = Post.getEntityManager
        em.getTransaction().begin()
        em.persist(p);   
        em.getTransaction().commit()
        return true
      end   

Test Adding a New Entry

  1. Return to the browser and click the new link. Add a new post.








Step 3: Viewing an Entry

Code View

  1. Replace the contents of show.rhtml with the following:


      <p>
        <b>Title:</b> <%= h @post.title %>
      </p>
      <p>
        <b>Body:</b> <%= h @post.body %>
      </p>
     
      <%= link_to 'Edit', :action => 'edit', :id => @post %>
      <%= link_to 'Back', :action => 'list' %>

Code the Controller

  1. Add the following to blog_controller.rb:


      def show
        @post = Post.find(params[:id])
      end   
       

Code the Model

  1. Add the following to_param convenience method which I copied from base.rb:


      # Copied from base.rb. Allows us to specify post rather than post.id in list.rhtml.
      # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
      def to_param
        # We can't use alias_method here, because method 'id' optimizes itself on the fly.
        (id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
      end       
  2. Add the following private method:



      def self.find_from_id(id)
        result = getEntityManager.find(Posts.java_class, Long.new(id))

        post = Post.new
        post.id = result.get_id
        post.title = result.get_title
        post.body = result.get_body
       
        return post
      end   
       

Test Viewing and Entry

  1. Return to your browser, refresh the page (so the Permalink is generated correctly) and click the Permalink:



Step 4: Editing an Entry

Code the View

  1. Replace the contents of edit.rhtml with the following:

  2.   <h1>Editing post</h1>
     
      <% form_tag :action => 'update', :id => @post do %>
        <%= render :partial => 'form' %>
        <%= submit_tag 'Edit' %>
      <% end %>
     
      <%= link_to 'Show', :action => 'show', :id => @post %>
      <%= link_to 'Back', :action => 'list' %>
     

Code the Controller

  1. An edit and update methods to blog_controller.rb:


      def edit
        @post = Post.find(params[:id])
      end
     
      def update
        @post = Post.find(params[:id])
        if @post.update_attributes(params[:post])
          flash[:notice] = 'Post was successfully updated.'
          redirect_to :action => 'show', :id => @post
        else
          render :action => 'edit'
        end
      end 
     

Code the Model

  1. Add the update_attributes to post.rb:

      def update_attributes(attributes)
        p = Posts.new
        p.id = id
        p.title = attributes[:title]
        p.body  = attributes[:body]
        em = Post.getEntityManager
        em.getTransaction().begin()
        em.merge(p);   
        em.getTransaction().commit()
        return true   
      end
     

Test Editing an Entry

  1. Return to your browser and click the Edit link:







Resources

Related Topics >>

Comments

Belated thanks...

I'm moving from a Rails shop back to a Java shop and am just learning JPA... I wondered if this was practical and am pleased to see the experiment's been done for me. Many thanks!