#121
Aug 04, 2008

Non Active Record Model

This episode will show you how to make a model which isn't based on Active Record. You may want to do this if a resource isn't backed by the database.
Tags: models
Download (18.8 MB, 12:51)
alternative download for iPod & Apple TV (14.8 MB, 12:51)
# models/letter.rb
class Letter
  attr_reader :char
  
  def self.all
    ('A'..'Z').map { |c| new(c) }
  end
  
  def self.find(param)
    all.detect { |l| l.to_param == param } || raise(ActiveRecord::RecordNotFound)
  end
  
  def initialize(char)
    @char = char
  end
  
  def to_param
    @char.downcase
  end
  
  def products
    Product.find(:all, :conditions => ["name LIKE ?", @char + '%'], :order => "name")
  end
end

# letters_controller.rb
def index
  @letters = Letter.all
end

def show
  @letter = Letter.find(params[:id])
end
<!-- letters/index.html.erb -->
<% title "Select a Letter" %>

<% for letter in @letters %>
  <%= link_to letter.char, letter %>
<% end %>

<!-- letters/show.html.erb -->
<% title "Products for Letter #{@letter.char}" %>

<p><%= link_to "Select a Letter", letters_path %></p>

<%= render :partial => @letter.products %>

Full Source Code

RSS Feed for Episode Comments 16 comments

1. Eric Duminil Aug 04, 2008 at 07:23

Nice!

Note that if no model uses ActiveRecord in your app, you can add

<<<<<<<<<<<<<<
Rails::Initializer.run do |config|
  config.frameworks -= [:active_record ]
end
>>>>>>>>>>>>>>>

to your environment.rb

And what about caching Letter.all with

<<<<<<<<<<<<<<
 def self.all
    @@all||=('A'..'Z').map { |c| new(c) }
  end
>>>>>>>>>>>>>>>

?


2. cbmeeks Aug 04, 2008 at 07:28

Awesome as always!


3. Tim F Aug 04, 2008 at 07:29

How about using an anonymous scope for the products method. That way products would behave even more like an ar association.

Thanks for the great screencasts. Alway looking forward to Mondays.


4. Shuaib Zahda Aug 04, 2008 at 07:32

Thanks, it is great. in fact i need it in my application WebVZ


5. Ryan Bates Aug 04, 2008 at 08:04

@Eric, yep, caching the "all" method is a possibility. But I tend to not add this kind of caching up front unless I know for sure it is worth it.

Caching usually comes with some hidden costs. I couldn't, for example, cache the "products" method as well because that will result in all products staying in memory for my entire app.

If you determine adding a global cache is worth it (with @@all) then it's best to call "freeze" on each object in the cache. This way you ensure the state of the object doesn't change.

@Tim, that's a great idea. Using scopes instead of a simple find adds a lot of potential.


6. Kenneth Kalmer Aug 04, 2008 at 09:03

Good way to do it, just prompted me to update my activerecord-tableless-models plugin. Find the plugin at http://github.com/kennethkalmer/activerecord-tableless-models


7. Steven Aug 04, 2008 at 10:37

@Kenneth,

Here's what I did for quick and dirty validations without DB backing:

http://pastie.org/247701

[Admin Edit: moved to pastie]


8. Adam Hill Aug 04, 2008 at 20:50

Great stuff & good timing for a current project :)


9. Rob Anderton Aug 05, 2008 at 00:52

I'm looking forward to the day when ActiveModel (http://github.com/rails/rails/tree/master/activemodel) is complete and stable enough to use: it'll be good to create tableless models that can still use the cool stuff like validations and observers.

Until that day though...thanks for the screencast Ryan.

ps. oooh a preview button :D


10. Ryan Bates Aug 05, 2008 at 07:13

@Rob, I'm looking forward to Active Model as well. I think it will have a big impact in how we work with these kinds of models. I was hesitating to make this episode yet for that very reason, but figured it will be a while before Active Model is released.


11. Clemens Kofler Aug 05, 2008 at 11:46

Finally ... I've seen questions regarding this stuff at Railsforum and WorkingWithRails. Now, instead of explaining it myself, I can link to this screencast! ;-)

Small suggestion: You override the to_param method which is really cool. However, if you do that, you might as well override the to_s method for the Letter class because I think it's a really underused feature in Rails (and probably Ruby in general).


12. Ryan Bates Aug 05, 2008 at 15:52

@Clemens, I considered defining to_s here too, but decided not to. Although it may work well in this example, for the majority of cases I don't think it does. There are often many different ways to represent a model as a string.

I prefer to be explicit each time so there's no confusion as to how it's being displayed. For example, let's say we had a Month model which represented a specific month of the year. How would you display this as a string? The full month name? The abbreviated version? Would you include the year? etc.

I think this decision of how to represent a model as a string belongs in the view, not the model.


13. JR Aug 05, 2008 at 18:47

Ryan, thanks a bunch for this (and all screencasts).

I had a case similar to this in an app i'm working on and this is how i originally designed it.

But I was hesitant at first to have ActiveRecord methods inside a non-ActiveRecord class ... somehow it just didn't seem right. i thought the find methods should have been helpers in the controller, but this seems to make more sense.


14. pedz Aug 07, 2008 at 05:59

I discovered a few weeks ago that if you use a form_for for these models, you need to define the "id" method or you get nasty-grams in your web server log file.


15. Andy Ferra Aug 07, 2008 at 14:53

I think that the letters should be factored out of the 'all' method.

For example:
http://pastie.textmate.org/249561

What do you think Ryan?


16. Ryan Bates Aug 07, 2008 at 16:13

@Andy, what is the reasoning behind moving the letters into their own method? Is it for self documentation? I think the "all" method is simple enough to not warrant further refactoring.

However, if you need to add more characters to the "all" method in addition to alphabetical letters, then I would say refactoring out just those letters is worth it.


17. Andy Ferra Aug 07, 2008 at 19:28

@Ryan, it just feels a little more clear to me. To me, "all" isn't the dataset, the collection of letters is.

Also, I tend to preemptively factor things out that I know I'm going to refer to in a lot of different places.

Maybe, I'm getting ahead of the implementation but, I could easily see it becoming appropriate as the class grows. So why not just do it up front?


18. Cassiano Sep 15, 2008 at 07:18

Ryan,

There is a missing '=' before 'render' in:

http://github.com/ryanb/railscasts-episodes/tree/master/episode-121/store/app/views/products/index.html.erb

so instead of:

<% render :partial => @products %>

it should read:

<%= render :partial => @products %>

I have also noticed that the 'log' folder is not present, so it needs to be created manually (not a big deal, but providing it would avoid an unnecessary error when trying to boot up the development server).


19. Ryan Bates Sep 20, 2008 at 11:36

@Cassiano, thanks for pointing out this error! I'll update the code.


20. Ryan Heneise Oct 02, 2008 at 11:57

I just created a plugin called PassiveRecord that makes a lot of this stuff very easy. Check it out on Github: http://github.com/artofmission/passiverecord


21. Sjors Provoost Feb 01, 2009 at 19:55

Also very useful in this context (and a slightly different approach):

http://stackoverflow.com/questions/315850/rails-model-without-database


22. Gang Apr 20, 2010 at 06:44

Pretty cool! Thanks a lot.


23. Dating Love Site Jun 25, 2010 at 04:33

for your sharing titanium lug nuts


24. hoodia patch review Jul 13, 2010 at 22:30

http://www.hoodiapatchreview.blogspot.com
hoodia patch review,hoodia patch scam ,hoodia patch review ,hoodia patch side effects ,hoodia patch ingredients ,


25. Discount Nike Shoes Jul 27, 2010 at 01:06

Nike is always purchased by a large number of customers.You will be definitely like it for it's so splendid.


27. Exclusive Sneakers Aug 01, 2010 at 23:24

if i use a form_for for these models, you need to define the "id" method or you get nasty-grams in your web server log file.


28. free directory list Aug 11, 2010 at 22:29

It is amazing! Thanks!


29. Air Jordan AJF 3 Aug 14, 2010 at 01:31

Pretty cool! Thanks a lot.I think that the letters should be factored out of the 'all' method.


30. nextags Aug 20, 2010 at 03:57

If you determine adding a global cache is worth it (with @@all) then it's best to call "freeze" on each object in the cache. This way you ensure the state of the object doesn't change.


31. wholesale new era caps Aug 20, 2010 at 21:02

That is an awfully astounding column you've posted.Thanks a lot for that a fantastically amazing post!


32. louis vuitton shoes Aug 26, 2010 at 21:17

Thanks for sharing your article. I really enjoyed it. I put a link to my site to here so other people can read it. My readers have about the same interets


33. Wholesale Electronics Aug 27, 2010 at 00:43

Discount Wholesale Electronics, Wholesale Cell Phones, Electronic Gadgets and More from the Best Dropship Wholesaler


34. snow boots Aug 31, 2010 at 00:11

I have also noticed that the 'log' folder is not present, so it needs to be created manually (not a big deal, but providing it would avoid an unnecessary error when trying to boot up the development server


35. louis vuitton sunglasses Sep 01, 2010 at 22:22

Good post, I can’t say that I agree with everything that was said, but very good information overall:)

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player
Give Back to Open Source