Skip to content

Instantly share code, notes, and snippets.

@kpearson
Last active August 29, 2015 14:26
Show Gist options
  • Save kpearson/d497654f9ebdffbb7b4f to your computer and use it in GitHub Desktop.
Save kpearson/d497654f9ebdffbb7b4f to your computer and use it in GitHub Desktop.

Non-ActiveRecord Forms

Makikng non-ActiveRecord objects behave like ActiveRrecord objects is some times handy when handeling data stored outside of an app. When using a third party api we endup making fome a json responce. Useing model objects that do not inherit form ActiveRecord force us to use form_tag. This get the job done. However it's definitely feels a little clumsy to me, not to mention all code we end up repeaing.

The goal of this article is to show what is involved in faking out ActiveRecorde so we can leverage other tools which rely on Rails conventions. To do this I am going to use an older pice of Rails called ActiveResource which has been spun of into a gem. Their are several other ways to achive this end like the Her gem. I am all for any tools that work and produce clean readable code. For the purposes here I think ActiveResoures gives us an interesting view of the iner working of form_for.

When constructing forms using form_for Rails relies on ActiveRecord to provide information about the model object. First the form_builder needs to know if the model object is being created for the first time or if it's updating an existing object.

Lets add the active_resource gem:

# Gemfile

gem 'activeresource'

Run bundle

ActiveResource

ActiveResource needs a few things to get running. First let's require 'active_resource' at the top of the file. The class is going to inherit from active_resource. Last we need to give active resource a class veriable self.site = "http://api.example.com". Put that all together and my new model file looks like this this.

require 'active_resource'

class ModelName < ActiveResource::Base
  self.site = "http://api.example.com"
end

If you'd like to confirm everything is wired up correctly. You can jump into a console session (rails console) and call ModelName.ancestors. The output should reveal ActiveResource::Base in the inheritance hierarchy with all the classes it inherits from.

My console output (shortened for brevity):

ModelName.ancestors
=> [Event, ActiveResource::Base, ActiveResource::Reflection, ActiveModel::Serializers::Xml, ...]

Forms

Now that the object inherits from ActiveResource, Rails has what it needs to interact with it as if it were an ActiveRecord object. This get us much closer to using form_for' and being able to recycle the form partial. The way form_forknows whether to build a new form or an update form is thru a method calledpersisted?. If this returns true, the object it's called on already exists as a record and is then being updated. If object.persisted? returns false, the object is treated as a new entry in the database in the form builder sets up what http verb to user and how to name the submit button.

Persisted?

To give the model class access to set the state of persisted? we need to add a setter method which we can call on an instance of that class.

require 'active_resource'

class ModelName < ActiveResource::Base
  self.site = "http://api.example.com"
  
  def persisted=(arg)
    @persisted = arg
  end

end

Now in the edit controller action we can call @object.persisted = true and the form builder will make the correct assumptions about how to construct the form.

  def edit
    @event  = Event.find(params[:id], nbuilder)
    @event.persisted = true
  end

Attributes

In the new controller action we need to set up attributes which we are handing of to the external api. When were are updating the object ActiveResource takes care of this for us by turning all the json keys into accessible attributes. But with a new object we need to set these up.

<code example>

Faking Associations

Often we will have some kind of nested data. If we were using ActiveRecord this would be a standard issue assosiation which the form builder would take care of for us with a fields_for block and the help of the accepts_nested_attributes_for method.

In the case of an edit form with an object created from a json response. Its likely that there is some nested data we want the form to handle for us. What we have built so far doesn't know how to populating existing values into an edit form. This requires us to fake out a method created by the accepts_nested_attributes_for. What we need to is add a method called <name_of_nessted_hash_key>_arrtibutes to the model, which looks like this:

require 'active_resource'

class ModelName < ActiveResource::Base
  self.site = "http://api.example.com"
  
  def persisted=(arg)
    @persisted = arg
  end
  
  def contact_attributes=(attributes)
  end
  
end

Nothing else is needed. The _attributes method does not need anything inside. No associations like has_many or `belongs_to'. As long as the name of the method matches the symbol in the fields_for block the form builder object will know how to handle it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment