ActiveRecord::Base Without Table
Asked Answered
D

7

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.

Dragging answered 2/6, 2009 at 0:28 Comment(4)
possible duplicate of Rails model without databaseBobbi
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.Galway
The solution in 2016 https://mcmap.net/q/265861/-rails-model-without-databasePositively
I have released a gem, activerecord-tablefree, 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)Bluepencil
L
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>>
Loyceloyd answered 2/6, 2009 at 10:10 Comment(2)
Make the subclasses inherit the parent columns: https://mcmap.net/q/266826/-activerecord-base-without-tableHauptmann
@Xinzz I have released a gem, activerecord-tablefree, 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)Bluepencil
A
17

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

class MyModel
  include ActiveRecord::Validations

  # ...
end
Applause answered 2/6, 2009 at 2:56 Comment(1)
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...Dragging
B
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.

Barrier answered 29/11, 2011 at 23:37 Comment(2)
Thanks, wish that had been available in ROR 2 :)Dragging
You should be including ActiveModel::Validations, however. For more info, you can check out the Railscast on the topic, as well as this blog post by Yehuda Katz.Doff
D
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".

Doff answered 17/12, 2009 at 15:55 Comment(2)
Interesting. I've been using this codetunes.com/2008/07/20/tableless-models-in-rails for some time without problems. Thanks.Dragging
Yes, that solution works and does need the table_name patch, because in that case the class inherits directly from ActiveRecord::Base.Doff
H
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
Hauptmann answered 14/8, 2013 at 17:2 Comment(0)
E
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

Enate answered 25/10, 2011 at 20:53 Comment(4)
Hi @Neil Stockbridge, I'm not positive this answers the question at all.Dragging
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.Enate
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 objectEnate
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.Dragging
R
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.

Rocketeer answered 20/2, 2013 at 14:12 Comment(1)
I have released a gem, activerecord-tablefree, 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)Bluepencil

© 2022 - 2024 — McMap. All rights reserved.