Topic: How do you access the attribute in your join model? RESOLVED

My view is a contract data entry form, and all is working but for the fact that I cannot access an extra attribute that is in the join model?I have these models with the following association:

    class Contract < AR::Base 
      has_many :clientlines
      has_many :codelines
      has_many :clients, :through => :clientlines 
      has_many :codes, :through => :codelines

      accepts_nested_attributes_for :clients
      accepts_nested_attributes_for :codes

      attr_accessible :clients_attributes, :clients, :codes_attributes, :codes; :authnum, :st_date, :end_date, :units_alloc

     end

      class Clientline < AR::Base
         belongs_to :contract
         belongs_to :client

      end

      class Client < AR::Base
         has_many :clientlines
         has_many :contracts, :through => :clientlines

       end

       class Codeline < AR::Base
          belongs_to :contract
          belongs_to :code
          units_alloc

        end

        class Code < AR::Base
           has_many :codelines
           has_many :contracts, :through => :codelines
        end

In the new action of app/controllers/contracts_controller.rb I have:

        def new
          @contract = Contract.new
          @contract.codes.build
          @contract.clients.build
        end

The view app/views/contracts/new.html.haml

        - provide(:title, 'Add Contract')
        %h2 New Contract
        =form_for(@contract) do |f|
           =render 'shared/contract_erro

The partial app/views/contracts/_fields.html.haml

     <fieldset><legend>Enter Contract Details</legend>
       .field
          = f.label :name, "AuthNum"
          %br/
          = f.text_field  :authnum, :size => 10, :class => "ui-state-default"
       .field
       .
       .
      </fieldset>
      <fieldset><legend>Enter Client Details</legend>
       = f.fields_for :clients do |ff|
         .field
            = ff.label :name, "First Name"
            %br/
            = ff.text_field :f_name, :size => 15, :class => "ui-state-default"
         .field
         .
         .
      </fieldset>
      <fieldset><legend>Enter Billing Code Details</legend>
      = f.fields_for :codes do |ff|
         .field
            = ff.label :name, "Code Name"
            %br/
            = ff.text_field :code_name, :size => 15, :class => "ui-state-default"
         .field
         .
         .
       = f.fields_for :codelines do |ff|
         .field
           = ff.label :name, "Units Alloc"
           %br/
           = ff.text_field :units_alloc, :precision => 6, :scale => 2, :size => 10, :class => "ui-state-default"
     </fieldset>

After some extensive reading, Rails Guides and googling around this issue, I understood that I should have to build both the clients and codes attributes and that as a result of the accepts_nested_attributes_for they in turn will build the join tables, clientlines and codelines, attributes.

Well if I leave the code as it is the units_alloc does not show up in my view, but if I add the following line to app/controllers/contracts_controller.rb new action:

        @contract.codelines.build

The units_alloc field does show up in the contract view and if you examine the element it shows as:

    input#contract_codelines_attributes_0_units_alloc 

which is what I want since all the other fields have this format and also all the fields save to the appropriate tables.

But there is a problem, the @contract.codes.build builds a codelines entry with just all the fields associated with codes, eg; codelines_id+1, code_id and contract_id etc, and then the @contract.codelines.build builds another codelines entry and this time with just the fields associated with codelines, eg; codelines_id, contract_id the same as in codes and units_alloc. So in my codelines table I have two entries one for the codes and the other for the units_alloc.

I guess I am not too secure with this. Is it correct?

My thinking was that I could get access to the units_alloc through the codelines_attributes already built by codes?

Help ... Please.

Last edited by fuzzytom (2012-03-26 08:57:50)

Re: How do you access the attribute in your join model? RESOLVED

You have a couple of flaws in your design and you are going to hate me for pointing it out as you will have to refactor your code somewhat.

Firstly a code is associated to a code line NOT a contract therefore a code should know nothing about the contract at all other than a corresponding has_many through association and in that respect Rails is doing it's job very well. but please consider the following

Codes is a lookup table right?
consider a contract that is 2 years old.
What happens to the contract details if you update the code associated with it to cater for  change needed in new contracts?

I would argue that the contract once formed is set in stone and should not be related to any lookups,
so you should have a contract with contract lines and the contract lines should each "copy" the necessary code data from the code lookup at the time it is chosen.

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: How do you access the attribute in your join model? RESOLVED

Thanks for your reply @jamesw. I am delighted that you have pointed out areas that I need to work on. I have been at this for over a month and I definitely need help.

I have a sketch of what I am trying to do but I am not sure of how to post an image in the message. I am used to just uploading the image. I did try the Images link, but it seems that I have a url in the wrong place .. so it would not accept it.

Yes codes is a lookup.

But let me try and explain the issue:

In codelines I will have the following:

    codeline_id    contract_id     code_id     units_alloc
          01                  01                05              40
          01                  01                07              30
          02                  02                01              60
          02                  02                05              20

If a code has to change we normally just retire it, its status is now marked as in-active, that way it is still in the database, and we create a new code.

I am not sure how to setup the association if I remove contract out of the relationship?

I will have to refactor other areas of my code, but it is far better knowing that I am moving forward.

Just for the record I am not a programmer, that might be obvious, accounting is my area, but I developed some spreadsheets and access application that I though would be great as web applications. That is when I decided to learn rails, mostly from google and buying tutorials.... it is a long road but in this economic climate one has to do whatever it takes to keep yourself relevant.

Thanks.

Re: How do you access the attribute in your join model? RESOLVED

Expiring the code is fine, at least you have catered for changes.

I have some confusion about your relationships but first you show the code lines table like so

In codelines I will have the following:

    codeline_id    contract_id     code_id     units_alloc
          01                  01                05              40
          01                  01                07              30
          02                  02                01              60
          02                  02                05              20

You have a field called codeline_id  that should be just id as the primary key! Perhaps a typo?

For clarity I'll just pick out the salient points
Your contract relationships look like this

    class Contract < AR::Base 
      has_many :clientlines
      has_many :codelines
      has_many :clients, :through => :clientlines 
      has_many :codes, :through => :codelines

and you have

       class Codeline < AR::Base
          belongs_to :contract
          belongs_to :code
          units_alloc

        end

When you create a new contract you want to create clientlines, codelines and set the units_alloc on the codeline right?
This you say works (as I would expect it to) when you do @contract.codelines.build right?

The confusion seems to be then around the codes records. You say that you are trying to create a new code record for each codeline using

@contract.codes.build

but if code is a lookup that is wrong. In your view you should have a select or some other means of assigning an existing code to the code line. Using a select helper to provide a drop down selection list but you have a fieldset to enter new code details.

If you want to enter new code details then build the code from the codeline not the contract if you want to assign an existing code then use the select helper.

Your model relationships are set up correctly to handle either situation so you are nearly there. smile
If you can say which you wish to do then I can point you in the right direction

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: How do you access the attribute in your join model? RESOLVED

Thanks for your reply @jamesw,

Yeah, my initial thought on putting together the New Contract form was that I would use it to add new contracts, new clients and new codes. This essentially to populate the database. But I guess that is going to be a problem once we have a population of clients and codes and that on instances we are setting up a new contract for an existing client. Also, in many instances one has a contract with a particular constellation of codes, but another contract has its own constellation of codes some of which are in other contracts and many times the units_alloc against each code will all be different.

So maybe I will create two specific forms one just for the entry of new clients, and the other for the entry of new codes. Then on the existing New Contract form I can use the select helper for both clients and codes since they will all be existing.?

I do have some confusion about the nested attributes. From my reading I gather that I only need the following in my app/controllers/contracts_controller.rb new action:

       def new
           @contract = Contract.new
           @contract.codes.build
           @contract.clients.build
       end

The explanation is that @contract.codes.build, not only builds the code object but that it also builds the codeline object, therefore it is unneccessary for me to use the @contract.codelines.build in the app/controllers/contracts_controller.rb new action, since that would build yet another codeline object.

Well as I noted in my original posting, I left contract.codelines.build out initially and that resulted in the units_alloc not showing up in my view. When I added @contract.codelines.build in the app/controllers/contracts_controller.rb new action, then the units_alloc did show in the view and so I filled in all the fields and saved the form and this is what I got in my codelines table:

      id       contract_id       code_id       units_alloc
      33             18                                     80.00 .... @contract.codelines.build
      34             18                 17                          .... @contract.codes.build   

My expectation was and is, that codeline object would only be built once and that it would have say id=33 and contract_id=18, code_id=17 and units_alloc=80.00 all on one line.?

I have been testing this setup for a while now and each time I go into the database and look at the tables then I delete the rows and run another test so that is why the id' are up around 33 and 34.

So I guess the answer is 'yes' to the fact that @contract.codelines.build is working, except I am not sure if it is working correct?

To your point that codeline_id should be id ... yes that was a typo and lastly, I would like to get the existing New Contract Form to work and then later on I will work on developing the form such that it uses the selector helper to construct new contracts for existing clients and codes.

I really appreciate your help and patience in working through this with me.

Thanks.

Update

In re-reading you response I finally picked up on the point that I should build the code object through codeline. I modified my app/controllers/contracts_controller.rb file as follows:

        def new
           @contract = Contract.new
           codelines = @contract.codelines.build
           codeline.build_code
           @contract.codes.build
         end

If I leave out the last line @contract.codes.build, only units_alloc show up in my view under codes. If I add @contract.codes.build back in then all of the code attributes show in my view.

Once we go into the database though ... the codelines table still has two lines as illustrated above, but with  with id=35 and id=36.

Thanks.

Last edited by fuzzytom (2012-03-25 16:31:07)

Re: How do you access the attribute in your join model? RESOLVED

As far as selecting an existing or creating a new record there are various ways. I'm not suggesting that this is the best way but this stack overflow question has a javascript solution that you might find useful
http://stackoverflow.com/questions/4419 … new-option

As far as accepts nested attributes are concerned you have to remember that html forms are designed to provide a list of key value pairs that the controller action the form is submitted to can make sense of. The key value pairs in Rails are passed into a controllers action inside the params hash.
now when you tell a model to update it's attributes using data from the params hash (@some_model.update_attributes(params[:some_model) or SomeModel.new(params[:some_model) etc...) if the hash you provide it with has nested model data AND you have accepts nested attributes for declarations correctly defined in the models then the model will use the nested params from the prams hash to create or update associated records.

The real trick is to arrange for the form to nest the attribute properly for the params hash and that is what the fields_for declaration does.

<%= form_for @parent do |f| %>
...
  <%= f.fields_for :children do |child| %>
  <%end%>
<%end%>

In the above f.fields_for arranges the child records to be nested inside the main form_for attributes that's why you do f., leave out the f. and you will still get the child objects parameters in the params hash but they will be at the top level not nested inside the parent params.

Your new action will create the blank records that provide the empty fields for the fields_for declarations
If you don't build new records then the fields_for has nothing to work with as fields_for loops through the childrens array on the parent object and because you build new empty children records fields for will display empty fields

If you built 3 new empty records (3.times do parent.children.build) you would get 3 sets of input fields for the children in your form.
what you could do is BUILD the codelines, CREATE a code then assign the code to the codelines e.g.

  codeline = @contract.codelines.build
  code = Code.new
  codeline.code = code
  #or
  codeline.code.build

It is good practice to take a close look at your log file to see how params get nested inside the params hash. and you can see what your form is passing back to the controller actions. Your log file will become your friend rather rapidly once you know how to read it.

What you want and what you need are too often not the same thing!
When your head is hurting from trying to solve a problem, stop standing on it. When you are the right way up you will see the problem differently and you just might find the solution.
(Quote by me 15th July 2009)

Re: How do you access the attribute in your join model? RESOLVED

Thanks @jamesw,

You have given me a lot to work with and I now feel like I am on the move again. I like the idea of the logs since getting an idea of what the form is sending to the params hash has been missing in my strategy so far.

Thanks again.