Rails 4: form object for update action for 2 models - forms

I have a form object that handles 2 associated objects. I'm trying to get some validation working on the form object, but am not having much success.
I think the issue is that the attributes I'm validating are delegated to the model objects, but I'm not sure how else to handle this situation: when the form is displayed, these attributes need to pull from the actual models to show the values on the form in edit mode, but when I update the form, I need to validate the submitted parameters, not the values being pulled from the models themselves.
One thought I have is using a EditBorrowerForm object that pulls attributes from associated models and a UpdateBorrowerForm that performs all the validations and updates the models. But that seems like a lot of extra work for something trivial.
I've seen a lot of form objects used for CREATE actions (where you only validate fields before passing them to the models), but haven't seen any good examples of UPDATE actions (where you need to both pull fields from models as well as update them).
Here are the models I'm working with:
class Client < ActiveRecord::Base
has_many :borrowers
end
class Borrower < ActiveRecord::Base
belongs_to :client
end
And my form object:
class BorrowerForm
include ActiveModel::Model
attr_accessor :client, :borrower
delegate :first_name, :last_name, :date_of_birth, to: :client
delegate :citizenship, :marital_status, to: :borrower
validates :first_name, :last_name, :citizenship, presence: true
def initialize(borrower)
#borrower = borrower
#client = #borrower.client
end
def update_attributes(params)
# Never validates properly...
if valid?
ActiveRecord::Base.transaction do
borrower.update_attributes(params.slice(:citizenship, :marital_status)
client.update_attributes(params.slice(:first_name, :last_name, :date_of_birth)
end
true
else
false
end
end
end
And the controller:
class BorrowersController < ApplicationController
def edit
#borrower = BorrowerForm.new(Borrower.find(params[:id])
end
def update
#borrower = BorrowerForm.new(Borrower.find(params[:id])
if #borrower.update_attributes(params)
redirect_to :index
else
render :edit
end
end
end

Related

Select Tag, undefined method `map'

I have a subscriptions form where I am trying to set the plan the user has chosen, the business they would like to associate with the subscription and the payment details. In my form I use a select tag to display a list of all of the businesses and it displays properly in my view but upon save I get the following error:
undefined method `map' for #<Business:0x007f8ea7955b90>
new.html.erb
<div class="field">
<%= select_tag :business_id, options_from_collection_for_select(#businesses, "id", "name") %>
</div>
subscriptions_controller.rb
...
def new
#subscription = Subscription.new
#plan = Plan.find(params["plan_id"])
#businesses = Business.all
end
def create
#subscription = Subscription.new(subscription_params)
raise "Please, check subscription errors" unless #subscription.valid?
#subscription.process_payment
#subscription.save
redirect_to #subscription, notice: 'Subscription was successfully created.'
rescue => e
flash[:error] = e.message
render :new
end
private
def set_subscription
#subscription = Subscription.find(params[:id])
end
def subscription_params
params.require(:subscription).permit(:plan_id, :business_id, :card_token, :coupon)
end
Am I setting up the select_tag properly? Do I need to fix my create method? Looked at other solutions on SO with little success.
Rails instantiates a new controller for every request. There's some information about that here.
This means that any instance variables you've set in new won't be available when you process the POST in create.
In your case, you are rendering the :new template in the rescue block of the create action when the new subscription fails validation. You're only getting the error at this point, not when you originally access the form.
The problem is that render :new doesn't call the new action; it just renders the template. In the case where the subscription fails validation and the form is re-rendered, the new action has never been called by this controller instance, and the instance variables do not have the values expected by the template.
Try this instead of render :new:
redirect_to new_subscription_url
This will instantiate a new controller and call the new action, so that you will be starting over with a clean slate. The instance variables needed in the new template will be assigned the correct values before the template is rendered.
As an alternative, you could set the instance variables in the rescue block:
def create
...
rescue => e
flash[:error] = e.message
#businesses = Business.all
render :new
end
Here's a similar question on Stack Overflow.
Hope that helps. Happy coding!

FactoryGirl to_create return value

From my understanding, the return value from a factory's 'to_create' method is ignored. This means that the object returned from the 'build' or 'initialize_with' portion of the factory is the object ultimately returned when calling 'create' within a test.
In my case, I am using a variant of the Repository Pattern. I am overriding the 'to_create' portion of the factory to include a call to a repository 'save' method. This method does not modify the given object, but returns an object representing the persisted form of the original.
However, the instance returned from the 'build' block is returned from the factory, and not the instance created in the 'to_create' block. In my code, this means the "unpersisted" form of the object is returned, not the object with updated attributes (e.g. 'id') from the saving action.
Is there a way of forcing the return value of 'create' to be either the result of the 'to_create' block or some value generated within that block?
class Foo
attr_accessor :id, :name
...
end
class FooRepository
def self.create(name)
Foo.new(name) # this object is not yet persisted and has no .id
end
def self.save(foo)
# this method must not guarantee that the original Foo instance
# will always be returned
...
updated_foo # this is a duplicate of the original object
end
...
end
FactoryGirl.define do
factory :foo, class: FooRepository do
# create an example Foo
initialize_with { FooRepository.create(name: "Example") }
# save the Foo to the datastore, returning what may be a duplicate
to_create {|instance| FooRepository.save(instance)}
end
end
describe FooRepository do
it "saves the given Foo to the datastore" do
foo = create(:foo)
foo.id #=> nil
...
end
end
I don't have an answer for you beyond "raise an issue", sorry.
The default to_create callback looks like this:
$ grep to_create lib/factory_girl/configuration.rb
to_create {|instance| instance.save! }
The main problem is that ActiveRecord modifies itself in place when you call save! on it. FactoryGirl will ignore any new objects that are returned from to_create.
A quick hack if you want to override the default create strategy:
module FactoryGirl
module Strategy
class Create
def association(runner)
runner.run
end
def result(evaluation)
evaluation.object.tap do |instance|
evaluation.notify(:after_build, instance)
evaluation.notify(:before_create, instance)
instance = evaluation.create(instance) # <-- HACK
evaluation.notify(:after_create, instance)
end
end
end
end
end
... Or do this to your to_create hook to mimic Rails' in-place modification:
to_create do |record|
new_record = YourRepositoryHere.new.create(record)
record.attributes = new_record.attributes # For example
new_record # Return the record just in case the bug is fixed
end
Best of luck. :(

Validation on DataTable when new row is added

When a new row was added programmatically using an auto generated method on my strongly typed DataTable, How can I fire my custom validation which validate the maxleng of my field?
My client (C#)
DAL.ImportMarcDataSet.PublicationsRow newRow = importMarcDataSet.Publications.NewPublicationsRow();
newRow.CallNumber ="QA76.76.A65";
newRow.Title = "Programming WCF services";
newRow.ISBN = "0596526997";
importMarcDataSet.Publications.AddPublicationsRow(newRow);
My Data Access Layer (VB)
Partial Class ImportMarcDataSet
Partial Class PublicationsDataTable
Private Sub CallNumberMaxLength(ByVal pRow As PublicationsRow)
If pRow.CallNumber.Length > 25 Then
pRow.SetColumnError("CallNumber", "The value entered is over the maximum length")
Else
pRow.SetColumnError("CallNumber", "")
End If
End Sub
'this event is ok when user made changes to the CallNumber column of the current row
Private Sub PublicationsDataTable_ColumnChanged(ByVal sender As Object, ByVal e As System.Data.DataColumnChangeEventArgs) Handles Me.ColumnChanged
If e.Column Is Me.CallNumberColumn Then
CallNumberMaxLength(e.Row)
End If
End Sub
End Class
End Class
You can handle the table's RowChanging event. When the DataRowChangeEventArgs.Action is equal to Add or one of the change... actions do your validation.
It has been a long time since I did this, but I believe you can even cancel the edit if needed by calling CancelEdit on the DataRowChangeEventArgs.Row. Check the documentation. See Handling DataTable Events (ADO.NET) at http://msdn.microsoft.com/en-us/library/w9y9a401.aspx.
The TableNewRow will not help because it is only raised when NewRow is called.

factory_girl - has_many relationships and refreshing the parent model

I keep running into the same issue, and I would be surprised if I am the only person experiencing this and expect someone has a better way of doing this. When I create a Factory which has a dependent Factory (association), then the parent model is not updated with the model that has been added. Probably easier to explain in code.
Say I have:
Factory.define :company do |a|
a.name 'Acme'
end
Factory.define :office do |a|
a.name 'London'
a.association :company, :factory => :company
end
and I execute this code:
london = Factory.create(:office)
sanfran = Factory.create(:office, :name => 'San Fran' , :company = london.company)
then if I run this test
london.company.offices.count.should eql(2)
it fails, because company Acme was instantiated before London or even San Fran were created, and because company.offices.new was not used to create the new models, the company model was never updated.
The only way I have been able to work around this issue is to write my tests as follows:
london.company(true).offices.count.should eql(2)
which forces a refresh.
However, this is really not ideal to do this every time in my tests, especially when the code it is testing should not have to rely on that.
Is there a reason you can't create the parent company first? I don't seem to have a problem getting a count from a pre-instantiated parent after creating child objects.
describe Company do
describe "office associations" do
before(:each) do
#company = Factory(:company)
end
it "should have the correct number of offices" do
o1 = Factory(:office, :company => #company)
o2 = Factory(:office, :company => #company)
#company.offices.should =~ [o1, o2].flatten # <= Not sure why, but each call to Factory appears to return an array
end
end

How to Use ActiveResource with Shallow Nested Routes?

I have a Rails application that has a Company resource with a nested resource Employee. I'm using shallow routing, so to manipulate Employee, my routes are:
GET /employees/1
PUT /employees/1
DELETE /employees/1
POST /companies/1/employees
How can I create, read, update, and destroy Employees using ActiveResource?
To create employees, I can use:
class Employee < ActiveResource::Base
self.site = "http://example.com/companies/:company_id"
end
But if I try to do:
e=Employee.find(1, :params => {:company_id => 1})
I get a 404 because the route /companies/:company_id/employees/:id is not defined when shallow routes are used.
To read, edit, and delete employees, I can use:
class Employee < ActiveResource::Base
self.site = "http://example.com"
end
But then there doesn't seem to be a way to create new Employees, due to the lack of the companies outer route.
One solution would be to define separate CompanyEmployee and Employee classes, but this seems overly complex.
How can I use a single Employee class in ActiveResource to perform all four CRUD operations?
I'm using Rails 3.0.9. You can set the prefix like this:
class Employee < ActiveResource::Base
self.prefix = "/companies/:company_id/"
end
And then
Employee.find(:all, :params => {:company_id => 99})
or
e = Employee.new(:name => "Corey")
e.prefix_options[:company_id] = 1
It will replace :company_id with the value from prefix_options.
There is a protected instance method named collection_path that you could override.
class Employee < ActiveResource::Base
self.site = "http://example.com"
def collection_path(options = nil)
"/companies/#{prefix_options[:company_id]}/#{self.class.collection_name}"
end
end
You would then be able to this to create employees.
e = Employee.new(:name => "Corey")
e.prefix_options[:company_id] = 1
e.save
It doesn't seem like the prefix_options is documented other than in the clone method so this might change in future releases.
See this article: http://blog.flame.org/2009/11/04/activeresource-and-shallow-nested-routes.html
Here, the author proposes to override class method collection_path. This makes sense, since this method is used also by new_element_path and will retrieve the same path in both cases.
Example:
class Employee < ActiveResource::Base
self.site = "http://example.com"
def self.collection_path(prefix_options = {},query_options=nil)
super
"/companies/#{query_options[:company_id]}/#{collection_name}.#{format.extension}#{query_string(query_options)}"
end
end
Then you can find employees for a company by doing:
company = Company.find(:first)
Employee.find(:all, :params => {:company_id => company.id })
I found it best to override ActiveResource::Base.element_path with the same functionality as defined in the library, but omitting the use of prefix_options in the returned value. There is no
class Employee < ActiveResource::Base
self.site = 'http://example.com'
self.prefix = '/companies/:company_id/'
# Over-ride method and omit `#{prefix(prefix_options)}` from the returned string
def self.element_path(id, prefix_options = {}, query_options = nil)
"/#{collection_name}/#{URI.parser.escape id.to_s}.#{format.extension}"
end
end
The Employee class will then behave as usual, with no need to assign prefix_options to the instance as suggested in other solutions.