0

I'm new to Ruby and so far find it confusing as compared to my normal programming language, C#. The current thing I'm struggling with is I have a class which extends ActiveRecord::Base but represents a model that is not saved to the database, just used in memory. The purpose of this is so that I can use ActiveRecord's validation functionality.

Model

class NewsletterSignup < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :email, :zip

  def self.columns()
    @columns ||= [];
  end

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

  def persisted?
    false
  end

  column :first_name, :string
  column :last_name, :string
  column :email, :string
  column :zip, :string

  validates :first_name, :last_name, :email, :zip, :presence => true
  validates :email, :format => /^[-a-z0-9_+\.]+\@([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
  validates :zip, :format => /^\d{5}$/
end

Partial View

<%= simple_form_for NewsletterSignup.new ... do |f| %>
    <%= f.input :first_name, :label => "First Name:" %>
    <%= f.input :last_name, :label => "Last Name:" %>
    <%= f.input :email, :label => "Email:" %>
    <%= f.input :zip, :label => "Zip:" %>

    ...
<% end %>

I want to pass an empty model to the form so the validation works. But this gives me an error when trying to instantiate a NewsletterSignup without providing the attributes/values:

ActiveModel::MissingAttributeError missing attribute: first_name

What am I missing here?

Josh M.
  • 26,437
  • 24
  • 119
  • 200

1 Answers1

3

You're going about this backwards if you don't have a database table behind this model. Instead of inheriting from ActiveRecord::Base, just make a regular class and include the components you do need from Rails. Here's a StackOverflow answer that shows this: Ruby on Rails: Fully functional tableless model. Or if you're using Rails 4, by any chance, it's even easier now as all you have to do is include ActiveModel::Model. Here's an article on that: http://blog.remarkablelabs.com/2012/12/activemodel-model-rails-4-countdown-to-2013

Community
  • 1
  • 1
pdobb
  • 17,688
  • 5
  • 59
  • 74
  • 1
    Also this post by a Rails core member: http://blog.plataformatec.com.br/2012/03/barebone-models-to-use-with-actionpack-in-rails-4-0/ – Marcelo De Polli Sep 16 '13 at 18:15
  • @pdobb - thanks for that, it seems to work well. Like I said, I'm new to Ruby/Rails - can you elaborate what the purpose of the `class << self` and `def initialize(attributes = {})` blocks is in the answer you linked? What function do those blocks serve to make this work? – Josh M. Sep 16 '13 at 18:35
  • `class << self` is an alternative way to write class methods. The more common pattern is just: `def self.`. So in this case he's just defining the `def self.all` class method to return an empty array. Then `def initialize(attributes = {})` is the method that is called when a new object is instantiated from a class. So `MyObject.new(a: 1)` would call `initialize` passing the hash `{a: 1}` to it as attributes. You can override the `initialize` method in this way if you ever want to set attributes on object instantiation. – pdobb Sep 16 '13 at 18:42
  • Not to drag this out, but I thought I could initialize the instance like: `MyObject.new(:a => "a", :b => "b")` ... ? – Josh M. Sep 16 '13 at 18:50
  • No problem. Yes, that's how you create a new instance of `MyObject`. But, what the `.new()` method call does is it looks for a method on the object called `initialize` to handle the passed in params and/or do any object initialization stuff for the new instance. And you don't need to define `initialize` if you don't want to initialize new object instances with anything special. If you don't define it then the Ruby default `initialize` method will be used. – pdobb Sep 17 '13 at 00:49