One of the concepts I learned today was the use of named scopes in Rails. The concept is that you can predefine a db query, which can be dynamically added to any normal method call on the Model. For example, lets say you have 2 tables, Customer, and Address. A Customer can have one or many Address records. So, I want to capture that in my Customer model. The default functionality for the Customer model would be to return one, or many, Customer records, using the find() method. Which is good, but I also want to return the Address information as well. Consider the following Customer model:
class Customer < ActiveRecord::Base
has_many :addresses, :dependent > :delete_all, :foreign_key => 'person_id'
named_scope :with_address_info, {
:select => "customers.*, addresses.*",
:joins => "JOIN addresses on addresses.person_id = customers.id",
:order => "customers.last_name asc" }
end
The above code is all I need for a simple Customer model, customer.rb. The has_many identifier tells Ruby that the Customer can have many Address records. The "with_address_info" named scope allows me to dynamically include the Address records with any query. In my Controller, I can have the following code:customers = Customer.find(:all)
customerFull = Customer.with_address_info.find(:all)
That's all. Pretty powerful stuff. That small model file has given me the ability to Create, Retrieve, Update, and Delete (CRUD), all in 7 lines of code. Technically, if I don't care about the named scope, the size of the file would be 3 lines of code. But that seems pretty scary. It is like magic. Obviously, it isn't magic. The Rails framework writes all of those extra lines of code for you behind the scenes.So, does it really take away control from a programmer, more than Java? Well, if you are use to manually creating JDBC calls to a database, then YES. You have a lot more control with the DB access in the code itself when you work with the straight JDBC drivers.
But, the need for that level of control is rare. Many of us use Object Relational Mapping (ORM), like Hibernate or Java Persistence API (JPA). In that case, Java takes the JDBC overhead away as well as the Rails framework. Of course, the syntax between Ruby and Java is such that a Ruby Model is much smaller in lines of code then a JPA entity (all those getter and setters add up). There is also the dynamic typing that I won't get into. But if you consider the "magic" with mapping a software object to a database object, then they are performing the same actions. Consider the following JPA Entity:
import java.io.Serializable;
import javax.persistence.*;
import java.util.Set;
@Entity
@Table(name="customers")
public class Customer implements Serializable {
@Id
private int id;
@Column(name="customer_type")
private String customerType;
@Column(name="email_address")
private String emailAddress;
@Column(name="first_name")
private String firstName;
@Column(name="last_name")
private String lastName;
@OneToMany(mappedBy="customer")
private Set<Address> addresses;
public Customer() {
}
public int getId() {
return this.id;
}
public void setId(int id) {
this.id = id;
}
public String getCustomerType() {
return this.customerType;
}
public void setCustomerType(String customerType) {
this.customerType = customerType;
}
public String getEmailAddress() {
return this.emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
public String getFirstName() {
return this.firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return this.lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Set<Address> getAddresses() {
return this.addresses;
}
public void setAddresses(Set<Address> addresses) {
this.addresses = addresses;
}
}
The Customer entity is a lot more wordy then the Customer Rails Model, but it performs the same kind of magic, just a little differently. If you were to use this Entity in a session EJB, you could have the following code:List customers = entityManager.createQuery(" from Customers c ");
This code already has a reference to the address records. You can just call the getAddresses() method:for (Customer customer : customers) {
List<Address> addresses = customer.getAddresses();
....
}
So, overall, Ruby on Rails provides a quick, and less verbose method of getting data across multiple tables. But if you use some popular ORM packages, you really arn't losing the control you thought you had.