Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Thursday, February 7, 2008

Eager loading with cascaded associations

Finder Methods, Eager loading with cascaded associations


Cascading association in rails is a wonderful development which allows you to use the Model Associations very easily and comfortably. Here with i will be explaining few techniques to use that:

Normal find method in rails is like this,

Author.find(:all)

With conditions,

Author.find(:all, :conditions => ".....")

Now let us suppose we are having a relation between a author and book, like an Author has many books and Book belongs_to author

To find books written by a particular Author, we will use some thing like

Author.find(:all, :include => :books)

Author.find(:all,:include => [:books,:novels]) if at all we are having association with the novels table also and if we want to retrieve the novels too.


But here comes the new area which i am about to tell you specifically.

That is if we are having a relation between Table A and Table B. Another relation between Table B and Table C.
But in one finder method if we wish to find the table C's content, querying through Table A, it is not possible. So we will be using cascading associations at this situation:

Starting from scratch,

:include Option recognizes the following options

Symbol - Base table simply Joins it.
Eg: Author.find(:all, :include=>:books)

String - Same as Symbol
Eg: Author.find(:all, :include => "books")

Array - The Base table joins those elements directly
Eg: Author.find(:all,:include=>[:books,:novels,:magazines])

Hash - The base table JOINS the KEY part and it JOINS the VALUE part.(Here value is processed as same as the :include option)
Eg: Author.find(:all,:include=>{:books,:movies})

Now i will explain about the depth of such kind of joining tables. Well , to say, that is not limited.As of now, i will explain uptil 2 and +2 methods

Normal with one association: One level deep
Author.find(:all, :include=>:posts)
=> authors
+ posts

Normal with two associations: One level deep

Author.find(:all, :include=>[:posts, :categorizations])
=> authors
+- posts
+- categorizations

Cascading Associations with two levels

Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
=> authors
+- posts
+- comments
+- categorizations

Author
.find(:all, :include=>{:posts=>[:comments, :categorizations]})
=> authors
+- posts
+- comments
+- categorizations

Cascading associations with more than two levels....

cascaded in three levels
>> Company.find(:all, :include=>{:groups=>{:members=>:favorites}})
=> companies
+- groups
+- members
+- favorites


>> Author.find(:all, :include=>{:posts=>{:author=>{:posts=>...}}})
=> authors
+- posts
+- author
+- posts
+- ...


It also has a feature that automatically aliases table name when conflicted. So we can use eager loading on same table names such as acts_as_tree.

TreeMixin.find(:all, :include=>"children")
=> mixins
+- children




Tuesday, January 15, 2008

Merb and Data Mapper

A couple of new frameworks making waves in the Ruby community are Merb and Data Mapper. Merb and Data Mapper seem to go together like peanut butter and jelly in that Merb gives you pretty much everything Rails gives you except for an ORM framework, and Data Mapper is an ORM framework like Active Record.

Data Mapper has a fantastic Why Data Mapper? page. I love this page because it assumes you the reader are a Ruby on Rails web developer, so you know Active Record, and you want to know why you'd want to use Data Mapper instead of Active Record. After reading that page, I was definitely intrigued. It's amazing what good documentation can do for the success of a framework. Merb has documentation on the differences between Merb and Rails as well.

The most obvious difference that jumps out at you between Active Record and Data Mapper is that in the Data Mapper, you define the properties (a.k.a fields, attributes, columns) of your models inside the model itself, as opposed to Active Record, where you define no properties in your model and reflect the properties from the columns in your database. This has long been a point of contention for many developers critical of Rails.

The most obvious difference between Rails and Merb is that with Merb, controller actions always return the data that is to be sent to the client. A Merb action can simply return a String, or it could return a File, and IO or even a Proc. That sounds cool.

It will be interesting to see if Merb/Data Mapper gain popularity in the Ruby community. Rails is a full-stack solution, where as Merb is a MVC framework and leaves it up to you to choose an ORM framework and Data Mapper is just an ORM, not tied to any one MVC framework. This is more like the Java web framework world, where you have MVC frameworks like Struts, Stripes, etc. and ORM frameworks like Hibernate, and you glue them all together with something like Spring or Guice. The difference here is that you glue them together with Ruby, still no XML or Annotations needed.

One thing I thought I liked about Rails is that it's the only web framework people in the Ruby community use to build web apps. In the Java world, managers and business types would say "We're a Java shop" or "We're building our app in Java", but the reality is that there are so many different frameworks and saying you are building it in "Java" could mean one of a hundred different things. That might really mean "JSF/Struts 2/Spring/Hibernate" or "Freemarker/Stripes/Guice/iBatis". Matt Raible has basically built his entire career around analyzing all the different options there are for Java web frameworks, as well as developing a meta-framework to help tie them all together. Up until now, on the Ruby side of things, if someone says we're building an app with Ruby on Rails, you know what you are talking about.

But it seems this could be the start of trend, where we get more frameworks within the Ruby community, which is a good thing because you can make choices, but on the other hand it could start to fragment the community. Overall I think it's a win and the reality you come to realize after developing with Rails for a year or so is that Ruby is really the thing that makes the whole thing go and that knowledge is easily portable to other Ruby frameworks. To make it even easier, Merb and Data Mapper both have many of the same concepts as Rails, so I can't imagine the learning curve of switching from one to the other to be particularly steep.

Another crazy idea is that with specialized frameworks like Merb and Data Mapper that are meant to be just one part of a stack rather than the full stack like Rails, and JRuby becoming a legitimate option, you could start to see some weird hybrid Ruby/Java apps, like Merb+Hibernate, or Stripes+Data Mapper. I doubt that would be common, but you could do it.

So talk is cheap, let's get something going. To install Merb and Data Mapper, I followed these instructions for setting up Merb and Data Mapper.

sudo gem install merb --include-dependencies
sudo gem install datamapper
sudo gem install ruby2ruby --include-dependencies
sudo gem install merb_datamapper
cd /usr/local/lib/ruby/gems/1.8/gems/datamapper-0.2.3
sudo bash -c "ARCHFLAGS='-arch i386' rake dm:install:mysql"

Believe it or not that actually worked for my on my Macbook. That last step just outputs the following if it works:

(in /usr/local/lib/ruby/gems/1.8/gems/datamapper-0.2.3)

Here's to hoping I can just do this at some point in the future:

sudo gem install merb_datamapper_mysql --include-dependencies

But you can't for now. So I continued following the instructions and got this error when I tried to run rake:

paulbarry@paulbarry: ~/projects/foo $ rake
(in /Users/paulbarry/projects/foo)
Using pure ruby JSON lib
rake aborted!
no such file to load -- json/pure
/Users/paulbarry/projects/foo/rakefile:10
(See full trace by running task with --trace)

Why didn't gem install merb --include-dependencies install json and json_pure for me if they are required dependencies? Good question, I don't know, but we'll just install them now to get things going:

sudo gem install json json_pure

So after that, everything goes off without a hitch. Picking up where the instructions left off, let's at least create a hello world app. Create a file in app/controllers called hello_world.rb with the following contents:

class HelloWorld < Application
def index
"

Hello, World!

"
end
end

Go to http://localhost:4000/hello_world and bask in the glory of your fist Merb web application. This should seem familar, except for the fact that you are naming it HelloWorld instead of HelloWorldController. How do you avoid namespace collisions when you have a User controller and a User model? Good question, I'll let you know when I find the answer.

So as promised, you can just return a string and that's when gets rendered in the browser. But you don't want to do that for real, so let's render a template. Create a file app/views/hello_world/index.html.erb and put the html in there. Change the index method to just call render, like this:

class HelloWorld < Application
def index
render
end
end

And there's our hello world with a template. Ok, so what about data mapper? So step one is create file in app/models called foo.rb with the following contents:

class Foo < DataMapper::Base
property :bar, :string
property :created_at, :datetime
property :updated_at, :datetime

validates_presence_of :bar
validates_length_of :bar, :minimum => 1
end

This also should make sense if you are Rails developer. The only difference is that we are defining our properties in the class, rather than in a migration. To create our table and a test record, start the merb console, which you do by executing merb -i, and then enter these commands:

irb(main):004:0> DataMapper::Base.auto_migrate!
=> [Foo]
irb(main):005:0> Foo.create(:bar => 'Hello, World!')
=> #

Ok, so them we modify our controller and view to use this:

class HelloWorld < Application
def index
@foo = Foo.first
render
end
end

and:

<%= @foo.bar %>


And there we go, Merb and Data Mapper working together as one.

Merb - Alternative for Rails?

Like Ruby on Rails, Merb (Mongrel + ERB) is an MVC framework. Unlike Rails, Merb is ORM-agnostic, JavaScript library agnostic, and template language agnostic, preferring plugins that add in support for a particular feature rather than trying to produce a monolithic library with everything in the core. In fact, this is a guiding principle of the project, which has led to third-party support for the ActiveRecord, DataMapper, and Sequel ORMs.

In addition, it means that the core code in Merb is kept simple and well organised. This has multiple benefits. It means it‘s faster for one thing. It‘s also easier to understand, maintain and extend.

Get Merb

The simplest way to get Merb is to install the gem:

  $ sudo gem install merb --include-dependencies

If you want to contribute (or just use the latest code), you can build the gem from the svn trunk:

  $ sudo gem install mongrel json json_pure erubis mime-types rspec hpricot mocha rubigen haml markaby mailfactory Ruby2Ruby -y
$ svn co http://svn.devjavu.com/merb/trunk merb
$ cd merb
$ rake install

To generate a new merb app after the gem is installed:

  $ merb myapp

Dependencies

Currently, Merb itself depends on the following gems:

  • mongrel
  • json_pure
  • erubis
  • mime-types
  • rspec
  • rubigen
  • ruby2ruby
  • rake

** If you are on windows see this blog post oj how to get up and running: www.ghostonthird.com/2007/11/17/merb-on-windows-it-works/

You must also have either the json or json_pure gem installed. Note that the json gem provides a faster library but will not work with jRuby.

Optionally, merb can take advantage of the following gems:

  • mailfactory (if you wish to use merb‘s mailers)
  • haml 1.8 or greater (if you wish to use HAML templates, i.e. .haml files)
  • markaby (if you wish to use Markaby template, i.e. .mab files)
  • builder (if you wish to use Builder templates, i.e. .rxml, .rerb or .builder files)
  • memcache-client (for use with Danga Interactive‘s memcached)
  • swiftiply
  • eventmachine
  • rcov
  • ruby-debug (if you want to use debugging functionality)

You will also probably need to install your ORM of choice as well as any gem plugins you want to use (see below).

The merb server

right now you add your routes in the appdir/config/router.rb file. So by default it runs on port 4000

        $ cd /path/to/your/merb/app
$ merb

Or to start merb on a different port:

        $ merb -p 3500

To start a cluster of merb servers you specify the first port and then how many servers you want spawned. SO this command will start a merb instance on ports 3000, 3001, 3002

        $ merb -p 3000 -c 3

To start a Merb IRB console where all your models and other classes are pre loaded use the -i flag

        $ merb -i

To enable ruby-debug support, start Merb with

  $ merb -D

Now you can use the debugger method everywhere in your code to open an rdebug session.

To see all the available command line flags use:

        $ merb -h

Using Merb

Merb uses the Model-View-Controller (MVC) pattern. Incoming requests are matched in the Router and directed to an appropriate controller action.

Model

Merb does not come with its own model layer. While you are free to use whatever data system you like, the merb core team does maintain plugins for the following Object Relational Mappers (ORM‘s):

ActiveRecordThe same ORM that Rails uses. (sudo gem install merb_activerecord)
DataMapperFairly new ORM. (sudo gem install merb_datamapper)
SequelFairly new ORM. (sudo gem install merb_sequel)

To use your choice ORM, install the appropriate gem and uncomment the appropriate use_orm line in Merb.root/config/dependencies.rb

Controllers

(Merb.root/app/controllers/*)

Merb controllers inherit from Merb::Controller and contain built in view/template rendering. Incoming requests are usually mapped to a specific method in a controller. For example, using the default routes, a request to www.yourapp.com/posts/show/1 would call the the show method of your PostsController (and params[:id] would = 1.)

The return value of your action function gets sent back to the client as the view. In most cases, you are going to want to end your functions with a call to render. By default, render will render the view template associated with your action (in default merb that would be Merb.root/app/views//.html.erb - see the View section for more info.)

Controllers can be generated by calling Merb.root/script/generate controller ControllerName. By default, generated controllers inherit from the Application class (Merb.root/app/controllers/application.rb) which itself inherits from Merb:Controller. Application is a good place to put code pertinent to all controllers. An example would be setting a filter to check if a user is logged in or to preload user data for each controller.

before and after filters

Use the before method in your controllers. before accepts either a symbol, string or a Proc/lambda object. If you give it a symbol it will call a method with the same name as the symbol. If you give it a proc that takes one argument it will call the proc with the current controller as that argument. You can use :only and :exclude as options to your filters to exclude or include actions from certain filters. :only and :exclude take :symbols or [:sym, :sam] array of symbols.

        class Foo < only =""> :foo
before lambda {|c| c.headers['X-Foo] = 'bar' }, :exclude => [:foo, :baz]

def setup_user
# blah blah
end

def foo
# blah
end

def regular_action
# blah
end

end

To stop the before filter chain you use throw :halt with a few options:

        # halts the filter chain and calls filters_halted which you can override
# in your controller to specialize it.

throw :halt

# halts the filters and calls the method named after the symbol:

throw :halt, :other_action

# halts the filter chain and returns the result of the Proc being called

throw :halt, Proc.new{ |c| c.redirect "/foo" }

# halts the chain and returns whatever is in the string

throw :halt, "

You don't have permissions dude!

"

or even render templates:

throw :halt, render 'foo'
throw :halt, partial 'foo'

After filters accept a symbol, string or Proc and call that proc with the controller:

        after Proc.new {|c| Tidy.new(c.body) }, :only => :index

Views

(Merb.root/app/views/*)

A view can be loosely defined as any data sent back to the client (a "view" of your data.) By default, Merb controllers send the return value of your controller action as the view. The Controller#render method simply renders the specified view and returns it as a string. By default, a call to render without any options renders the view template associated with that controller action. Using the default ERB templating system, this means that a call to render in Posts#index would render the file Merb.root/app/views/posts/index.html.erb

Layouts

(Merb.root/app/views/layout/*)

Layouts are generic templates in which your specific view templates are rendered. A sample layout could look like:

 
My Application Layout



<%= catch_content :layout %>



By default, render will look for a corresponding layout for your controller in the form of Merb.root/app/views/layout/.html.erb . If no specific layout is present, render will attempt to use Merb.root/app/view/layout/application.html.erb

See render for more details/options, as well as how to use different templating systems in your app.

You can return several different types of values from your controller actions:

  • String: Any string will get sent to the browser as standard text/html
  • File/IO: Any file descriptor will get handed over to mongrel to be streamed to the client.
  • Proc Object: The object will be called and the return value sent to the client.

That last point has some cool connotations if you think about it. Merb does have a mutex lock around the call to your controller’s action anywhere that you can call AR objects. Merb’s lock is way smaller then rails giant lock though and allows for many more concurrent requests to be handled by one process. By returning a Proc object from your action, you allow merb to release the lock and the proc is called in multi threaded way. This allows for all kinds of cool streaming and ‘futures’ where you return the proc and release the mutex. It’s basically like handing over the proc to mongrel and mongrel handles calling it in a thread safe manner.

Helpers

(Merb.root/app/helpers/*)

app/helpers/global_helper.rb will be available to all of your views.

 Helpers named after your controller plus _helper.rb will be included in the views
for that controller only.

File uploads

When a file is uploaded with Merb, it gets put in a Tempfile. So you just want to copy it to the right place on the filesystem.

        def upload
puts params[:file].inspect
FileUtils.mv params[:file][:tempfile].path, Merb.root+"/uploads/#{params[:file][:filename]}"
render
end

A file upload will have a hash of params like this:

        {
:filename => File.basename(filename),
:content_type => content_type,
:tempfile => ,
:size => File.size(body)
}

Merb app layout

        merb_app:

app
controllers
helpers
mailers
models
parts
views
config
gems
lib
log
public
Rakefile
script
spec
test
unit