Unexpected naming of fields using Reform Form Object with Composition - ruby-on-rails

ANSWER: The model calls were redundant. There should only be one and the last one in this example was the winner. I was misusing the Form Object DSL. :/
I've got a Reform Form object in a Rails 4.1 form that is structured like...
Form Object
class MyForm < Reform::Form
include Composition
model :user
model :user_group
property :name, on: :user_group
property :email, on: :user
end
Controller
# ...
#form = MyForm.new(user: User.new, user_group: UserGroup.new)
# ...
View
<%= form_for(#form) do |f| %>
<%= f.text_field(:name) %>
<%= f.email_field(:email) %>
<% end %>
Rendered HTML
<input type="text" name="user_group[name]" id="user_group_name">
<input type="email" name="user_group[email]" id="user_group_email">
My question is why are the fields seeming to ignore the model mappings and rendering them to the wrong model name? What am I doing incorrectly here?

You can call ::model only once! Once you call it with the correct model (or any name), the rendering will name the fields to whatever ::model you specify. The point about Reform is to hide internals about your model names!

Related

Rails fields_for returning nil

Think I've solved this in the process of writing it. I'll post my solution below.
tl;dr Rails fields_for not generating the expected params.
I have a User class, with the following code:
class User < ActiveRecord::Base
has_one :woojit, dependent: :destroy, validate: true
accepts_nested_attributes_for :woojit
# ... more
end
and a Woojit class
class Woojit < ActiveRecord::Base
belongs_to :user
end
Our UsersController includes this method (we're using the gem Administrate, but it's a subclass of Rails' ApplicationController):
class WoojitsController < Admin::ApplicationController
def new
# ... other stuff
#woojit = #user.build_woojit
end
end
And our user _form.html.erb partial looks like this:
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
<!-- User fields -->
<fieldset>
<legend>Woojit fields</legend>
<%= f.fields_for #woojit do |ff| %>
<%= render 'woojit_fields', f: ff %>
<% end %>
</fieldset>
<% end %>
So when I submit the form, what I'm hoping to see is a params hash with this subset:
{
"user" => {
"woojit_attributes" => {
"attr_1"=>"Foo", # etc
}
}
}
But what I'm actually getting in the create action is a params hash with this subset:
{
"user" => {
"woojit" => {
"attr_1"=>"Foo", # etc
}
}
}
Ie 'woojit' instead of 'woojit_attributes'. I could hack this in the create action, but that seems like a horrible way of resolving the problem. I want to know why the key is getting mis-named to begin with.
I originally tried the alternative of using the line <%= f.fields_for :woojit do |ff| %> (ie symbol instead of instance var), and not building a Woojit object in the #new action.
The #fields_for guide suggests in the One-To-One section that this should work, but the code in the block was never executed, so the line returned nil - and the woojit params never made it to the create action in any form.
So the problem was apparently that #fields_for behaves differently when given an instance of a model (#woojit), rather than a symbol (:woojit).
In the former instance it generates fields that reference (model name), eg:
<input type="text" name="user[woojit][woojit_attr]" id="user_pledge_pledgor_name" />
In the latter instance it generates fields fields that reference the model attributes, eg:
<input type="text" name="user[woojit_attributes][woojit_attr]" id="user_pledge_attributes_pledgor_name" />
Hope that saves someone some of the frustration I've just been through!

How to send values that are not related to a model

I have a Post model that have a title and content attributes.
I want to make it possible to tweet when the user creates a post like this.
<%= form_for #post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :content %>
<%= f.check_box :with_tweet %> # Error
<%= f.text_field :tweet %> # Error
<% end %>
This code fails, because there are no with_tweet and tweet attributes for Post.
I don't think query strings are good idea situation like this.
How should I send a information that is not related a model?
Define them as virtual attributes:
class Post < ActiveRecord::Base
...
attr_accessor :with_tweet, :tweet
...
end
attr_accessor will provide a getter and a setter method for with_tweet and tweet which should then be accessible to you through the #post object.
Further to Vee's answer, let me explain why you'd need it:
Every attribute in a Rails model is the result of using a Ruby
getter & setter - meaning that your model's attributes are
essentially just parts of a Ruby object, built when your class is
initialized
The problem you have is Ruby on Rails does not build any other
attributes other than the ones in your DB, meaning if you wanted to
track extra data, you'll have to use virtual attributes to let
the class know to populate those not in the db
attr_accessor is used to define those extra attributes

attr_accessor not accessible in accept_nested_attributes_for

On my payments page there are certain variables such as card_number that I want to pass from the View to the Model but I do not want to store them in the db. I can usually easily achieve this by simply using attr_accessor but in this case the model is being passed in params through accepts_nested_attributes_for and for some reason the params are not being passed through:
in User.rb i have
has_many :credit_cards
accepts_nested_attributes_for :credit_cards
in the view file i have a nested form field, something like:
blah blah
<h2>Credit card</h2>
<%= f.fields_for :credit_cards do |builder| %>
<%= render "credit_card_fields", :f => builder %>
<% end %>
inside that
<p>
<%= f.label :test %><br />
<%= f.text_field :test %>
</p>
now back in credit_card.rb i have:
attr_accessor :test
before_create :show_me_test_param
private
def show_me_test_param
raise "#{test}"
end
Now the strange thing is that when I try to save a record, it simply returns an empty exception. The param does not seem to have been passed through from User to CreditCard through accepts_nested_attributes_for?
The param being passed in is:
{"email"=>"name#example.com", "password"=>"pass123", "password_confirmation"=>"pass123", "credit_cards_attributes"=>{"0"=>{"test"=>"helllo this is the second attempt", "name_on_card"=>"first lastname", "card_number"=>"987498742897", "card_verification_value"=>"232", "expiry_date"=>"2141"}}}
Does anyone know whats going on? Does accepts_nested_attributes_for work with attr_accessor?
This has messed me up several times in the past! Params for nested objects come to the controller with the key model_name_attributes which gets passed to the new or update_attributes method of the model in the controller.
So you'll need to add :credit_card_attributes to your attr_accessor to allow that key to be passed in.

Rails (3) validation of non-ActiveRecord field

I have a model with a corresponding form, for which I use ActiveRecord validations. At the bottom of the form I'd like to have a single confirmation checkbox which should not be persisted, but which must be checked for the form to be submitted. I'd also like any errors that stems from this checkbox not being checked to display alongside the ActiveRecord errors.
Now I could cobble something together in the controller manually, but I'm wondering if there is a built-in, cleaner way to handle this kind of situation?
I think you should add this in your model:
validates_acceptance_of :check_me
attr_accessor :check_me
attr_accessible :check_me # if you already have attr_accessible defined in your model
and this in your view:
<%= form_for #your_model do |f| %>
# some code
<%= f.check_box :check_me %>
<% end %>

Rails: how to save form data after posting

When I use form_for :model the data is saved when I submit the form.
However when I use form_tag, the data is lost after the form is processed.
I need to use form_tag because I have two models in one form.
Is there a way to save form data with form_tag?
You are making two incorrect assumptions in your question. First, form_tag is not necessary or even recommended for multiple-model forms; Second, form_tag doesn't do anything fundamentally different from form_for, you are most likely not formatting the field names correctly for your controller.
In order to create a form with nested models, you need to use the fields_for helper in conjunction with form_for. The relationship needs to be defined first in the model with accepts_nested_attributes_for. Since you have not given us any information about your models, I will give you a made-up example:
class Person < ActiveRecord::Base
has_one :address
accepts_nested_attributes_for :address
end
class Address < ActiveRecord::Base
belongs_to :person
end
This tells ActiveRecord that the Person model can accept attributes for Address, and will pass along the attributes to the correct model to be created.
<% form_for :person do |p| %>
<% p.fields_for :address do |a| %>
use the a form builder to create
fields for the address model here
<% end %>
<% end %>
chaining the fields_for helper from the p form builder lets the helpers generate attributes in the correct format.
More information: Nested Model Forms
Pretty much the same way as before except you'll need to build the params. You can look at your log to see how params are being sent.
eg.
def create
#silly_hat = SillyHat.new( :name => params[:name], :size => params[:size], :colour => params[:colour] )
if #silly_hat.save
...

Resources