ActiveModel - View - Controller in Rails instead of ActiveRecord?
Asked Answered
M

2

15

I'm trying to use ActiveModel instead of ActiveRecord for my models because I do not want my models to have anything to do with the database.

Below is my model:

class User
  include ActiveModel::Validations
  validates :name, :presence => true
  validates :email, :presence => true
  validates :password, :presence => true, :confirmation => true

  attr_accessor :name, :email, :password, :salt
  def initialize(attributes = {})
    @name  = attributes[:name]
    @email = attributes[:email]
    @password = attributes[:password]
    @password_confirmation = attributes[:password_confirmation]
  end
end

And here's my controller:

class UsersController < ApplicationController
  def new
    @user = User.new
    @title = "Sign up"
  end
end

And my view is:

<h1>Sign up</h1>

<%= form_for(@user) do |f| %>
<div class="field">
    <%= f.label :name %><br />
    <%= f.text_field :name %>
</div>
<div class="field">
    <%= f.label :email %><br />
    <%= f.text_field :email %>
</div>
<div class="field">
    <%= f.label :password %><br />
    <%= f.password_field :password %>
</div>
<div class="field">
    <%= f.label :password_confirmation, "Confirmation" %><br />
    <%= f.password_field :password_confirmation %>
</div>
<div class="actions">
    <%= f.submit "Sign up" %>
</div>
<% end %>

But when I load this view in the browser, I am getting an exception:

undefined method 'to_key' for User:0x104ca1b60

Can anyone please help me with this?

Many thanks in advance!

Marlin answered 17/7, 2011 at 4:38 Comment(1)
see #9817366Licence
A
33

I went rooting around the Rails 3.1 source to sort this out, I figured that would be easier than searching anywhere else. Earlier versions of Rails should be similar. Jump to the end if tl;dr.


When you call form_for(@user), you end going through this:

def form_for(record, options = {}, &proc)
  #...
  case record
  when String, Symbol
    object_name = record
    object      = nil
  else
    object      = record.is_a?(Array) ? record.last : record
    object_name = options[:as] || ActiveModel::Naming.param_key(object)
    apply_form_for_options!(record, options)
  end

And since @user is neither a String nor Object, you go through the else branch and into apply_form_for_options!. Inside apply_form_for_options! we see this:

as = options[:as]
#...
options[:html].reverse_merge!(
  :class  => as ? "#{as}_#{action}" : dom_class(object, action),
  :id     => as ? "#{as}_#{action}" : dom_id(object, action),
  :method => method
)

Pay attention to that chunk of code, it contains both the source of your problem and the solution. The dom_id method calls record_key_for_dom_id which looks like this:

def record_key_for_dom_id(record)
  record = record.to_model if record.respond_to?(:to_model)
  key = record.to_key
  key ? sanitize_dom_id(key.join('_')) : key
end

And there's your call to to_key. The to_key method is defined by ActiveRecord::AttributeMethods::PrimaryKey and since you're not using ActiveRecord, you don't have a to_key method. If you have something in your model that behaves like a primary key then you could define your own to_key and leave it at that.

But, if we go back to apply_form_for_options! we'll see another solution:

as = options[:as]

So you could supply the :as option to form_for to generate a DOM ID for your form by hand:

<%= form_for(@user, :as => 'user_form') do |f| %>

You'd have to make sure that the :as value was unique within the page though.


Executive Summary:

  • If your model has an attribute that behaves like a primary key, then define your own to_key method that returns it.
  • Or, supply an appropriate :as option to form_for.
Alva answered 17/7, 2011 at 5:45 Comment(5)
i'm really thankful for your effort matey! thats brilliant. One small question related to this as well. In case of ActiveRecord I had just added resources :users to my routes.rb file where users was my DB table and it worked out user paths according to id in the database. Now here its ActiveModel I'm using how do I approach defining user paths?Marlin
@Bilal: I'm not sure about the routes or which part of Rails or ActiveRecord handles that. I tend to add all my routes by hand and do most of the interaction through AJAX.Alva
thanks for this - your :as solution got me past the first error, but now I'm seeing undefined method persisted?'`Bonhomie
@KarlRosaen: I'd need more context to know what's going on with that.Alva
thanks for checking - for me it seems, when debugging the form_helper.rb code in rails 3.1, I found that it calls is_presisted? on the form object when rendering f.submit with no arguments, so I had to define it and return false to get the form_for to work. Alternatively, I can pass an argument, f.submit "submit form", and it works.Bonhomie
G
1

Looks like you should have instead investigated the (not very well documented) ActiveModel::Conversions class

https://github.com/rails/rails/blob/3-1-stable/activemodel/lib/active_model/conversion.rb

  include ActiveModel::Conversion

  def persisted?
    false
  end

would have done the trick, same applies to Rails 4.2

Gelya answered 14/6, 2016 at 21:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.