One-To-Many Model Update RoR - ruby-on-rails

I've got a one-to-many relationship from Treatment to Cost. The reason for this is because for invoicing and tax purposes we need to keep a record of changes in price for a treatment. So this is implemented by saying that the cost is equal to the most recent cost entry associated with that treatment.
This feature needs to be completely transparent to the user, they shouldn't know anything about the historical costs, just that there's a current one.
So when they hit edit, and do an update, if the cost of the treatment were to change, I then want to update the cost table also. The problem I have is with the models being represented in a form.
<% form_for([:admin, #treatment]) do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<p>
<%= f.label :description %><br />
<%= f.text_field :description %>
</p>
<p>
<%= f.label :duration %><br />
<%= f.text_field :duration %>
</p>
<p>
<%= f.submit 'Save' %>
</p>
<% end %>
So Treatment has_many :cost in the model, and in this form I want to have the cost field with the latest cost in it. For a start, how do I do that? Additionally, how do I update the cost model if the cost has changed? I'm assuming it's done in the controller, but if I have a text_field for the cost, how do I disassociate it with the #treatment model?
Cheers

I had a similar issue, so I had my has_many association but then I had a has_one association that pointed to the most recent version.
NOTE: I am using MSSQL, so your syntax might be a little different.
class Treatment < ActiveRecord::Base
has_many :costs
has_one :current_cost, :class_name => "Cost",
:conditions => "costs.id = (SELECT MAX(c.id)
FROM costs c
WHERE c.treatment_id = costs.treatment_id)"
end
I started with this:
class Treatment < ActiveRecord::Base
has_many :costs
def current_cost
self.costs.last(:order => "id")
end
end
But after it got large enough, I needed to minimize database dips and this was becoming a bottleneck.
If you had the ability of setting an enabled field in the costs model and using a validation to ensure that only one cost is enabled per treatment. Then I would recommend:
class Treatment < ActiveRecord::Base
has_many :costs
has_one :current_cost, :class_name => "Cost", :conditions => ["costs.enabled = ?", true]
end
I hope this helps!

Related

Correct usage of simple_form collection_check_boxes

I have set up 2 models as following :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title, :sector_ids
belongs_to :platform
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url
has_many :sectors
end
and a form which tries to use example from here as follows :
<%= simple_form_for #platform, :html => { :class => 'form-vertical'} do |f| %>
<%= f.error_notification %>
<div class="form-inputs">
<div class="row-fluid">
<div class="span12">
<div class="span6">
<%= field_set_tag "General" do %>
<%= f.input :name %>
<%= f.input :url %>
<%= f.collection_check_boxes :sector_ids, Sector.all, :title, :title %>
<% end %>
</div>
</div>
</div>
</div>
<div class="form-actions">
<%= f.button :submit %>
</div>
<% end %>
nevertheless, after I try to submit the form I get the following error :
Can't mass-assign protected attributes: sector_ids
What am I missing here? I successfully migrated the database after adding appropriate associations, but it seems like Rails doesnt really know what to do now wit the sector ids that are selected.
Solution :
class Sector < ActiveRecord::Base
attr_accessible :summary, :title
belongs_to :platforms
end
and
class Platform < ActiveRecord::Base
attr_accessible :name, :url, :platform_attributes, :sector_ids
has_many :sectors
end
and in the view :
<%= f.association :sectors, :as => :check_boxes %>
Ofcourse, do not forget to run "rake db"migrate", in case you havent done it yet. I was also required to restart the server in order for the changes to apply.
I hope this helps someone.
Try adding { :sector_ids => [] } to your Platform permits list.
https://github.com/rails/strong_parameters#nested-parameters
First I think you should remove :sectors_ids from the attr_accessible of Sector and try.
If the above doesn't works (which probably may not work), see the documentation of simple_form, specially read https://github.com/plataformatec/simple_form#associations and https://github.com/plataformatec/simple_form#collection-check-boxes; the last one makes what you are trying to do, but, in the associations they use has_and_belongs_to_many associations.
Finally, check this out https://github.com/plataformatec/simple_form/issues/341
Update
The solution is to make a has_and_belongs_to_many relationship, there is no way out, because, you want to associate some sectors to one platform, but, you want to associate the same sectors to other platforms, otherwise you won't need checkbox, only nested forms to keep adding new records. You have to make this change in your database design and structure.

Rails 3. How can I only display one nested model form?

I have these two models: Company and CompanyContact.
So I have the usual...
companies_controller.rb
def edit
#company = Company.find(params[:id])
student = #company.students.build
company_contact = #company.company_contacts.build
end
company.rb
has_many :company_contacts, :dependent => :destroy
accepts_nested_attributes_for :company_contacts, :reject_if => :reject_company_contacts, :allow_destroy => true
company_contact.rb
belongs_to :company
form.html.erb
<%= f.fields_for :company_contacts do |builder| %>
<%= render "company_contact_fields", :f => builder %>
<% end %>
_company_contacts.html.erb
<p style="margin:5px 0;">
<%= f.label :first_name %><br />
<%= f.text_field :first_name, :class => 'text_field' %>
</p>
<p style="margin:5px 0;">
<%= f.label :email %><br />
<%= f.text_field :email, :class => 'text_field' %>
</p>
In the edit form, if I already have a company contact for a company, it gives me that existing record plus another empty company contact form ready to be filled out. Which is ok because that it's supposed to happen.
What I need to do is only have ONE company contact, so if there is already a company contact, I don't want to display another form to add an extra company contact. I do not want to setup a has_one relationship because my client might want to add extra company contacts in the future and also when I tried a has_one relationship I got bunch of errors.
Ok so to only have ONE company contact per company, I tried a counter solution, you know in the loop set counter = 0 and then check if counter > 0 but that "solution" didn't work. What would you suggest?
If I got you correct, then
#company.company_contacts.build unless #company.company_contacts.present?
is what you are looking for.
This way, if a company already has a contact, then no more contacts are built. Likewise, if a company has no contact, this will build up contacts which shall then be used by f.fields_for :company_contacts to render in the form.

Nested model form not submitting everything

I heard about this community while listening to Hypercritical and I am excited to join in with my first question. I am working on my first rails App and I have run into an issue that I cannot seem to crack. I have been watching Railscast, Lynda.com, and Googling for days but I still cannot comprehend how to create a form that that will update my has_many :through associations at once. Allow me to try an explain what I am doing.
My Goal:
The firm I work for provides many "Service Offerings" and I want to be able to create a new service offering on one page and have it create the contacts and other information that is associated with it. The additional information such as "contacts" will live in their own tables because they may need to be referenced by many "Service Offerings."
Problem:
When I submit the form the "Service Offering" fields submit and are entered into the database, but the fields for the "Business Developer" do not. Obviously, I would like everything to be entered into its appropriate table and the for the IDs to be linked in the join table. I would really appreciate any insight that you could provide.
What I Have So Far: What you see below is Service Offerings and Business Developers. Eventually I will be adding Contacts, Photos, and Files but I thought I would start simply and work my way up.
Models:
class ServiceOffering < ActiveRecord::Base
attr_accessible :name, :description
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developer_service_offerings
end
class BusinessDeveloper < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :business_developer_service_offerings
has_many :service_offerings, :through => :business_developer_service_offerings
end
class BusinessDeveloperServiceOffering < ActiveRecord::Base
belongs_to :business_developer
belongs_to :service_offering
end
Controller:
def new
#service_offering = ServiceOffering.new
#service_offering.business_developers.build
end
def create
#service_offering = ServiceOffering.new(params[:service_offering])
if #service_offering.save
redirect_to(:action => 'list')
else
render('new')
end
end
View:
<%= form_for((#service_offering), :url => {:action => 'create'}) do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description%>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developer do |builder| %>
<p>
<%= builder.label :first_name%>
<%= builder.text_field :first_name %>
<%= builder.label :last_name%>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
I figured it out. It turns out a few things were wrong and needed to be changed in addition to the two suggestions #Delba made.
The Form:
I took a look at RailsCasts #196 again and noticed that my form looked different than the one used there, so I tried to match it up:
<%= form_for #service_offering do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developers do |builder| %>
<p>
<%= builder.label :first_name %>
<%= builder.text_field :first_name %>
<%= builder.label :last_name %>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Initially, this presented an error:
undefined method `service_offerings_path'
Routes:
This lead me to learn about RESTful Routes because I was using the old routing style:
match ':controller(/:action(/:id(.:format)))'
So I updated my routes to the new RESTful Routes style:
get "service_offerings/list"
resource :service_offerings
resource :business_developers
attr_accessible:
That got the form visible but it was still not working. So I did some searching around on this site and found this post that talked about adding "something_attributes" to your parent objects model under attr_accessible. So I did:
class ServiceOffering < ActiveRecord::Base
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developers
attr_accessible :name, :description, :business_developers_attributes
end
That change along with #Delba's suggestion shown in the above model and controller listed below solved it.
def new
#service_offering = ServiceOffering.new
#business_developer = #service_offering.business_developers.build
end
You just forgot to assign #business_developper.
def new
#service_offering = ServiceOffering.new
#business_developper = #service_offering.business_developpers.build
end
-
#business_developer = #service_offering.business_developers.build
initializes an instance of biz_dev which is then available in the view.
fields_for :biz_dev isn't really tied to this instance but to the many-to-many relationship btw serv_off and biz_dev.
In this way, you can add multiple input for additional biz_dev if you initialize another biz_dev instance in your controller. For instance:
5.times { #service_offering.biz_dev.build }
will add additional fields in your form without you having to declare them in your view.
I hope it helped.

db architechture of a time sheet application in RoR

As I am reading the rails 3 way I wanted to make a small app that will help me learn along with the book. I am thinking of making a simple timesheet application in rails, however, I can't figure out where to start as far as calendar functionality goes. This is what I have so far.
User has_many Timesheets
Timesheet belongs_to user
Timesheet will have date:datetime, hours:datetime and comments:string attributes.
how can I map a whole month to the timesheet?
For example.
if the user wants to fill timesheet for 06/01/2011 - 06/07/2011 will he have seven rows in the Timesheet table?
I would like someone to explain a brief architecture of ActiveRecord for this.
I would at least start out with something like this:
class User < ActiveRecord::Base
has_many :timesheets
end
class Timesheet < ActiveRecord::Base
belongs_to :user
has_many :work_days
end
class WorkDay < ActiveRecord::Base
belongs_to :timesheet
end
Timesheet will have a user_id:integer attribute.
WorkDay will have day:date, hours:integer, comment:text, and timesheet_id:integer attributes.
Then you can create timesheets with any collection of individual workdays you wish.
The form for a WorkDay could look something like this:
<%= form_for #work_day do |f| %>
<%= f.label(:timesheet, 'Timesheet') %>:
<%= f.collection_select(:timesheet_id, Timesheet.all, :id, :name) %><br />
<%= f.label :day, 'Date' %>:
<%= f.date_select :day %><br />
<%= f.label :hours, 'Hours worked' %>:
<%= f.text_field :hours %><br />
<%= f.label :comment, 'Comments' %>:
<%= f.text_area :comment %><br />
<% end %>
This will allow you to create a new WorkDay with all of its attributes and attach it to any existing Timesheet.
Note: This will look for a name method in Timesheet, which you could implement like this, for example:
def name
"Timesheet ##{self.id}"
end

Rails form with three models and namespace

Banging my head against this one for a long time. On Rails 2.3.2, Ruby 1.9.1.
Trying to use one form to create three objects that have these relations:
class Person
has_one :goat
end
class Goat
belongs_to :person
has_many :kids
end
class Goat::Kid
belongs_to :goat
end
Here's a summary of the schema:
Person
first_name
last_name
Goat
name
color
Goat::Kid
nickname
age
I'd like my #create action to instantiate new instances of all three models with the specified associations. However, while it appears that my params hash is being passed to the controller as it should (based on the backtrace logs in the browser when it blows up), the Goat::Kid object is not collecting the params.
irb (irb session is just a psuedo-representation of what I'm trying to accomplish so if it doesn't call #save! or any other necessities it's not really meant to be correct. I'm trying to do this all through the browser/web form.)
a = Person.new :first_name => 'Leopold', :last_name => 'Bloom'
b = Goat.new :name => 'Billy', :color => 'white'
c = Goat::Kid.new :nickname => 'Jr.', :age => 2
a.goat.kids
>> []
Now, I cannot figure out how to get the view to pass the params to each object and to get the controller to save these params to the db.
My questions: A) is this a good place to use nested_attributes_for and if so how do I declare that with a namespace? B) is there a much simpler, easier to understand way to do this?
Passing params to three models has just been very challenging to me and no matter how much documentation I read I can't wrap my head around it (#form_for and #fields_for). The namespace further complexifies this. Thanks for any help!
Addendum: if I end up declaring
accepts_nested_attributes_for
what's the proper way to use the symbol argument for a namespaced model?
accepts_nested_attributes_for :kids, :through => :goats
or
accepts_nested_attributes_for :goats_kids, :through => :goats
or
accepts_nested_attributes_for :goats::kids, :through => :goats
I'm not sure how namespaced models translate to their symbol identifiers. Thanks!
Well, this is my first time playing with accepts_nested_attributes_for, but with a little playing around I was able to get something to work.
First the model setup:
class Person < ActiveRecord::Base
has_one :goat
accepts_nested_attributes_for :goat
end
class Goat < ActiveRecord::Base
belongs_to :person
has_many :kids
accepts_nested_attributes_for :kids
end
class Goat::Kid < ActiveRecord::Base
belongs_to :goat
end
With a simple restful controller:
ActionController::Routing::Routes.draw do |map|
map.resources :farm
end
class FarmController < ApplicationController
def new
end
def create
person = Person.new params[:person]
person.save
render :text => person.inspect
end
end
Then comes the semi-complex form:
Next, the form setup:
<% form_for :person, :url => farm_index_path do |p| %>
<%= p.label :first_name %>: <%= p.text_field :first_name %><br />
<%= p.label :last_name %>: <%= p.text_field :last_name %><br />
<% p.fields_for :goat_attributes do |g| %>
<%= g.label :name %>: <%= g.text_field :name %><br />
<%= g.label :color %>: <%= g.text_field :color %><br />
<% g.fields_for 'kids_attributes[]', Goat::Kid.new do |k| %>
<%= k.label :nickname %>: <%= k.text_field :nickname %><br />
<%= k.label :age %>: <%= k.text_field :age %><br />
<% end %>
<% end %>
<%= p.submit %>
<% end %>
From looking at the source for accepts_nested_attributes_for, it looks like it will create a method for you called #{attr_name}_attributes=, so I needed to setup my fields_for to reflect that (Rails 2.3.3). Next, getting the has_many :kids working with accepts_nested_attributes_for. The kids_attributes= method was looking for an array of objects, so I needed to specify the array association in the form manually and tell fields_for what type of model to use.
Hope this helps.

Resources