wiring a dependent update

Rails 2.0.2 Ruby 1.8.5 SQLite3 CentOS-5.1

models/

Entity entity has_one client

Client client belongs_to entity

validates_associated

view/

clients/new.html.erb

<h1>New client</h1>

<%= error_messages_for :client %> <%= error_messages_for :entity %>

<% form_for(@client) do |f| %>

  <% fields_for(@entity) do |e| %>     <p>       <b>Client Name</b><br />       <%= e.text_field :entity_name %>     </p>

    <p>       <b>Client Legal Name</b><br />       <%= e.text_field :entity_legal_name %>     </p>

    <p>       <b>Client Legal Form</b><br />       <%= e.text_field :entity_legal_form %>     </p>

  <% end %>

  <p>     <b>Client status</b><br />     <%= f.text_field :client_status %>   </p> ...

controllers/ clients_controller.rb

  # POST /clients   # POST /clients.xml   def create     @entity = Entity.new(params[:entity])     @client = Client.new(params[:client])

    respond_to do |format|       if @entity.save && @client.save         flash[:notice] = 'Client was successfully created.'         format.html { redirect_to(@client) }         format.xml { render :xml => @client, :status => :created, :location => @client }       else         format.html { render :action => "new" }         format.xml { render :xml => @client.errors, :status => :unprocessable_entity }       end     end   end

What I want to do is to have an entity created if one does not exist and the existing entity.id returned to client otherwise. The above code almost works when there is no existing entity but the client.entity_id is not set to the id of the newly created entity throwing an SQL error.

Question 1. Must I create a method in clients.rb, say entity_build, that checks for the existence of an entity (find_by_entity_name) or creates a new entity otherwise? What would this look like?

Question 2. What do I pass the method from the controller? @entity, @client?

Question 3. How do I get the fields from the view into the entity from within the client model? See Q.2

Question 4. Is there a simpler way do do this?

I am twisting in the breeze her trying to figure out how to make this work. I have read any number of tutorials and code examples but I am just not getting it.

Any help is most welcome.

Okay - I've never understood the has_one principle. If :entity has_one :client, then why wouldn't you just put the fields for :client in the :entity model?

If it was :entity has_many :clients, then the way you would get client.entity_id populated would be as follows:

    @entity = Entity.new(params[:entity])     @client = Client.new(params[:client])     @entity.clients << @client

The result of that last line is that the new client is updated with the appropriate entity_id and then saved. So, in a has_one maybe it's like this...

    @entity = Entity.new(params[:entity])     @client = Client.new(params[:client])     @entity.client = @client

But I'm just not sure - the has_one thing is confusing to me, and from what I've read it actually works backwards from what you would expect if you're used to has_many.

Can you fill me in a little more on what you're doing - I'm almost certain there's a better design out there just waiting for you to find it. :slight_smile:

Cayce Balara wrote:

Okay - I've never understood the has_one principle. If :entity has_one :client, then why wouldn't you just put the fields for :client in the :entity model?

My background is data design and I am a newcomer to ruby and rails. I have examined both the language and framework on and off for the past few years but I am now beginning a real project. Currently, my main problem is getting enough traction with the API to know where to go to find answers. This is coming, albeit slowly.

Now, with respect to the has_one / belongs_to relationship or, as rails puts it, association; consider the situation at hand. What is a client? Well, that depends upon your point of view but essentially a client is someone that consumes your output and sends you money. Now, what is a vendor? Well, similarly a vendor is someone whose output you consume and whom you send money to. The salient question here is: are these two concepts, clients and vendors, separate things or are they separate roles of a single thing?

In many system designs these two concepts are treated as separate things. They possess state like account balance and physical attributes like addresses. They are related to transactions like invoices and cheques. However, considered another way, client and vendor can be profitably considered as just roles played by a distinct entity, whose existence is independent of whether or not they buy from or sell to us.

If one considers a client and a vendor simply as roles within a transaction based system then entity may be factored out and become a separate concept. A single entity may then possess the role of client (has_one :client) or vendor (has_one :vendor) (or both or neither) and also possess multiple attributes (has_many :locations, has_many :contacts). The role of client and vendor may not persist (client.status = "inactive") even when the other role does (vendor.status = "preferred"). An entity may be something else than a client or a vendor, perhaps an employee, a consignee for a drop shipment, an agent for the firm, or a person working at a client. Perhaps it is significant to know whether two or more entities are related (has_many :relationships).

Extracting the entity from its roles permits entity.contacts where contacts:

belongs_to :entity, :foreign_key => :entity_id belongs_to :entity, :foreign_key => :contact_entity_id

You can now reflect into a single representation of entity properties located in a group of consistently named and structured tables whether the entity is considered a client or a vendor in context.

Given this you also can have things like shipments:

  has_many :orders, :through => :order_shipments   has_one :entity, :foreign_key => :consignee_entity_id,                           :include => [:locations, :contacts]   has_one :entity, :foreign_key => :carrier_entity_id,   ...

You can also handle situations like issuing cheques to non-vendors (refunds to clients) and receiving funds from non-clients (employee reimbursement for disallowed expenses) in a much more natural manner since you are not required by the data design to set up a vendor in the first or a client in the second instance.

If it was :entity has_many :clients, then the way you would get client.entity_id populated would be as follows:

    @entity = Entity.new(params[:entity])     @client = Client.new(params[:client])     @entity.clients << @client

In my design an entity either has a role or it does not. A single entity is not treated as being multiple clients. In certain circumstances one might wish to treat divisions of a very large corporate entity as separate clients and this design could handle that eventuality, but that is not the case at present. On the other hand, if these division were structured as distinct legal forms then the current approach remains valid and a relationship model accommodates the reality.

The result of that last line is that the new client is updated with the appropriate entity_id and then saved. So, in a has_one maybe it's like this...

    @entity = Entity.new(params[:entity])     @client = Client.new(params[:client])     @entity.client = @client

This will fail with an SQL error as client.entity_id will be null. I know, I've tried ;-).

But I'm just not sure - the has_one thing is confusing to me, and from what I've read it actually works backwards from what you would expect if you're used to has_many.

What is bedeviling me at the moment is my state of profound ignorance with the Rails APi. There is nothing else for it but to read the damn thing again but, as with many things, what you read at leisure seldom returns when you need it. I have since rediscovered the API for ActiveRecord::Associations::ClassMethods and have therein learned that the way to do what I want is eventually going to look somewhat like this:

In clients_controllers.rb

  # GET /clients/new   # GET /clients/new.xml   def new     @entity = Entity.new     @client = @entities.build_client ...

  # POST /clients   # POST /clients.xml   def create     @entity = Entity.new(params[:entity])     @client = @entity.build_client(params[:client])

Based on my very preliminary and presently shallow understanding, the build_client method is generated by AR in consequence of the has_one / belongs_to association specification in the respective models. This then wires the underlying dependencies wherein client.entity_id is set to entity.id. In this case the AR association also enforces the foreign_key constraint so that if @entity.save fails then too does the dependent @client.save.

If I were to wrap this in a transaction (ActiveRecord::Transactions::ClassMethods) then it appears that I need to employ entity.save! and client.save! to force a transactional roll back in the event of an error in either. However, subject to further study and a deeper apprehension on my part, a transactional approach does not seem to provide any measurable benefit over the basic behaviour in this simple case.

Thank you for presenting the opportunity to clarify this to myself. It is amazing how having to explain oneself to someone else straightens things out in your own mind.