23

This came up a bit ago ( rails model attributes without corresponding column in db ) but it looks like the Rails plugin mentioned is not maintained ( http://agilewebdevelopment.com/plugins/activerecord_base_without_table ). Is there no way to do this with ActiveRecord as is?

If not, is there any way to get ActiveRecord validation rules without using ActiveRecord?

ActiveRecord wants the table to exist, of course.

Community
  • 1
  • 1
Dan Rosenstark
  • 64,546
  • 54
  • 267
  • 405
  • possible duplicate of [Rails model without database](http://stackoverflow.com/questions/315850/rails-model-without-database) – Ciro Santilli新疆棉花TRUMP BAN BAD Jul 22 '14 at 11:33
  • In Rails 3 you can include ActiveModel::Validations, as many other modules in that same namespace that'll bring ActiveRecord-like functionality to your models. In rails 4 there is also ActiveModel::Model, which includes many of them to make you feel your (non-persisted or custom-persisted) model like an ActiveRecord model. – nandilugio Sep 17 '14 at 12:45
  • 2
    The solution in 2016 http://stackoverflow.com/a/34354961/5310342 – Alex Rodriguez Lopez Apr 15 '16 at 08:48
  • I have released a gem, activerecord-tablefree, https://github.com/boltthreads/activerecord-tablefree which is compatible with Rails 5, and implements this pattern (the columns override used by activerecord-tablefree stopped working, had to switch to a new approach) – Peter H. Boling Dec 15 '17 at 03:31

7 Answers7

39

This is an approach I have used in the past:

In app/models/tableless.rb

class Tableless < ActiveRecord::Base
  def self.columns
    @columns ||= [];
  end

  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default,
      sql_type.to_s, null)
  end

  # Override the save method to prevent exceptions.
  def save(validate = true)
    validate ? valid? : true
  end
end

In app/models/foo.rb

class Foo < Tableless
  column :bar, :string  
  validates_presence_of :bar
end

In script/console

Loading development environment (Rails 2.2.2)
>> foo = Foo.new
=> #<Foo bar: nil>
>> foo.valid?
=> false
>> foo.errors
=> #<ActiveRecord::Errors:0x235b270 @errors={"bar"=>["can't be blank"]}, @base=#<Foo bar: nil>>
John Topley
  • 107,187
  • 45
  • 188
  • 235
  • Make the subclasses inherit the parent columns: http://stackoverflow.com/a/18237894/217956 – jpemberthy Aug 14 '13 at 17:03
  • @Xinzz I have released a gem, activerecord-tablefree, https://github.com/boltthreads/activerecord-tablefree which is compatible with Rails 5, and implements this pattern (the columns override stopped working, had to switch to a new approach) – Peter H. Boling Dec 15 '17 at 03:29
17

Validations are simply a module within ActiveRecord. Have you tried mixing them into your non-ActiveRecord model?

class MyModel
  include ActiveRecord::Validations

  # ...
end
Steve Madsen
  • 12,454
  • 4
  • 46
  • 65
  • Haven't tried that, very interesting... I kind of wanted to get the free initialize method and that as well, but with just validations I'll be happy... – Dan Rosenstark Jun 02 '09 at 08:40
9

I figure the more answers the better since this is one of the first results in google when searching for "rails 3.1 models without tables"

I've implements the same thing without using ActiveRecord::Base while including the ActiveRecord::Validations

The main goal was to get everything working in formtastic, and below I've included a sample payment that will not get saved anywhere but still has the ability to be validated using the validations we all know and love.

class Payment
  include ActiveModel::Validations
  attr_accessor :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state, :zip_code, :home_telephone, :email, :new_record

  validates_presence_of :cc_number, :payment_type, :exp_mm, :exp_yy, :card_security, :first_name, :last_name, :address_1, :address_2, :city, :state

  def initialize(options = {})
    if options.blank?
      new_record = true
    else
      new_record = false
    end
    options.each do |key, value|
      method_object = self.method((key + "=").to_sym)
      method_object.call(value)
    end
  end

  def new_record?
    return new_record
  end

  def to_key
  end

  def persisted?
    return false
  end
end

I hope this helps someone as I've spent a few hours trying to figure this out today.

bloveless
  • 4,272
  • 1
  • 14
  • 12
  • Thanks, wish that had been available in ROR 2 :) – Dan Rosenstark Dec 01 '11 at 00:57
  • You should be including `ActiveModel::Validations`, however. For more info, you can check out the [Railscast](http://railscasts.com/episodes/219-active-model) on the topic, as well as this [blog post by Yehuda Katz](http://yehudakatz.com/2010/01/10/activemodel-make-any-ruby-object-feel-like-activerecord/). – Dimitar Jan 16 '12 at 15:07
8

UPDATE: For Rails 3 this can be done very easy. In Rails 3+ you can use the new ActiveModel module and its submodules. This should work now:

class Tableless
  include ActiveModel::Validations

  attr_accessor :name

  validates_presence_of :name
end

For more info, you can check out the Railscast (or read about it on AsciiCasts) on the topic, as well as this blog post by Yehuda Katz.

OLD ANSWER FOLLOWS:

You may need to add this to the solution, proposed by John Topley in the previous comment:

class Tableless

  class << self
    def table_name
      self.name.tableize
    end
  end

end

class Foo < Tableless; end
Foo.table_name # will return "foos"

This provides you with a "fake" table name, if you need one. Without this method, Foo::table_name will evaluate to "tablelesses".

John Topley
  • 107,187
  • 45
  • 188
  • 235
Dimitar
  • 941
  • 1
  • 8
  • 9
  • Interesting. I've been using this http://codetunes.com/2008/07/20/tableless-models-in-rails/ for some time without problems. Thanks. – Dan Rosenstark Dec 17 '09 at 17:29
  • Yes, that solution works and does need the table_name patch, because in that case the class inherits directly from `ActiveRecord::Base`. – Dimitar Dec 27 '09 at 00:50
2

Just an addition to the accepted answer:

Make your subclasses inherit the parent columns with:

class FakeAR < ActiveRecord::Base
  def self.inherited(subclass)
    subclass.instance_variable_set("@columns", columns)
    super
  end

  def self.columns
    @columns ||= []
  end

  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end

  # Overrides save to prevent exceptions.
  def save(validate = true)
    validate ? valid? : true
  end
end
jpemberthy
  • 7,243
  • 6
  • 41
  • 51
1

There is the activerecord-tableless gem. It's a gem to create tableless ActiveRecord models, so it has support for validations, associations, types. It supports Active Record 2.3, 3.0, 3.2

The recommended way to do it in Rails 3.x (using ActiveModel) has no support for associations nor types.

Jarl
  • 2,726
  • 4
  • 20
  • 30
  • I have released a gem, activerecord-tablefree, https://github.com/boltthreads/activerecord-tablefree which is compatible with Rails 5, and implements this pattern (the columns override used by activerecord-tablefree stopped working, had to switch to a new approach) – Peter H. Boling Dec 15 '17 at 03:30
1

This is a search form that presents an object called criteria that has a nested period object with beginning and end attributes.

The action in the controller is really simple yet it loads values from nested objects on the form and re-renders the same values with error messages if necessary.

Works on Rails 3.1.

The model:

class Criteria < ActiveRecord::Base
  class << self

    def column_defaults
      {}
    end

    def column_names
      []
    end
  end # of class methods

  attr_reader :period

  def initialize values
    values ||= {}
    @period = Period.new values[:period] || {}
    super values
  end

  def period_attributes
    @period
  end
  def period_attributes= new_values
    @period.attributes = new_values
  end
end

In the controller:

def search
  @criteria = Criteria.new params[:criteria]
end

In the helper:

def criteria_index_path ct, options = {}
  url_for :action => :search
end

In the view:

<%= form_for @criteria do |form| %>
  <%= form.fields_for :period do |prf| %>
    <%= prf.text_field :beginning_as_text %>
    <%= prf.text_field :end_as_text %>
  <% end %>
  <%= form.submit "Search" %>
<% end %>

Produces the HTML:

<form action="/admin/search" id="new_criteria" method="post">
  <input id="criteria_period_attributes_beginning_as_text" name="criteria[period_attributes][beginning_as_text]" type="text"> 
  <input id="criteria_period_attributes_end_as_text" name="criteria[period_attributes][end_as_text]" type="text">

Note: The action attribute provided by the helper and the nested attributes naming format that makes it so simple for the controller to load all the values at once

Dan Rosenstark
  • 64,546
  • 54
  • 267
  • 405
  • Hi @Neil Stockbridge, I'm not positive this answers the question at all. – Dan Rosenstark Oct 28 '11 at 13:48
  • It's a working example of using Base without a table ( which was the original question) including the code to make Base work without a table. This example works for me with Rails 3.1.0. The others didn't. I thought this might be of use to others. – Neil Stockbridge Nov 12 '11 at 23:34
  • Validation is not included in the example above but since the object on the form is an `ActiveRecord::Base`, validation works just as you would expect with any other ActiveRecord object on a form. The example is perhaps cluttered by the inclusion of the nested object – Neil Stockbridge Nov 16 '11 at 23:02
  • Thanks... I'll have to try it, I guess. Looking at it, I cannot see how the attributes added get considered as first-class columns. Perhaps they don't need to be.... anyway, +1 for now, thanks. – Dan Rosenstark Nov 17 '11 at 01:43