ActiveRecord, has_many and _count

Hi everybody!

Here are trunks of models from a little rails app I'm developing:

···

###########################
class Country < ActiveRecord::Base
  has_many :companies
........

###########################
class Category < ActiveRecord::Base
  has_many :companies
........

###########################
class Company < ActiveRecord::Base
  has_many :employees , :dependent => true
  belongs_to :country, :counter_cache => true
  belongs_to :category, :counter_cache => true
........

###########################

class Employee < ActiveRecord::Base
  belongs_to :company, :counter_cache => true
........

###########################

I just would like to know: The right way to create a new company by updating
Country.find(id).companies.size is by doing
Country.find(id).companies.create . Right?

1) But what if I'd like to keep in cache companies.size for the category in
which the new company is put? I can't create a company simultaneously using
two parents!

2) When I edit the properties of a company, let's say moving from Japan to
Italy, how could I update Japan.companies_count and Italy.companies_count?
I tried to modify the update action of CompanieController:

###########################

  def update
    @company = Company.find(params[:id])
    @oldcountry=@company.country
    if @company.update_attributes(params[:company])
      @newcountry=@company.country
      @oldcountry.companies_count=@oldcountry.companies(:refresh).size
      @newcountry.companies_count=@newcountry.companies(:refresh).size
      flash[:notice] = 'Company was successfully updated.'
      redirect_to :action => 'show', :id => @company
    else
      render :action => 'edit'
    end
  end

###########################

but that did'nt work. :frowning:

3) Is there a Rails' way to cache the number of Employees directly in
Company.employees_count and Category.employees_count (i.e. through
a "has_many_many" relation)? That would prevent iterating through every
company of a given country/category to get its .employees_count!

Thanks a lot for your attention,

Have a good night,

Eric

I think you want the Rails mailing list...

Max

···

On 8/29/06, Éric DUMINIL <eric.duminil@gmail.com> wrote:

Hi everybody!

Here are trunks of models from a little rails app I'm developing:

###########################
class Country < ActiveRecord::Base
  has_many :companies
........

###########################
class Category < ActiveRecord::Base
  has_many :companies
........

###########################
class Company < ActiveRecord::Base
  has_many :employees , :dependent => true
  belongs_to :country, :counter_cache => true
  belongs_to :category, :counter_cache => true
........

###########################

class Employee < ActiveRecord::Base
  belongs_to :company, :counter_cache => true
........

###########################

I just would like to know: The right way to create a new company by updating
Country.find(id).companies.size is by doing
Country.find(id).companies.create . Right?

1) But what if I'd like to keep in cache companies.size for the category in
which the new company is put? I can't create a company simultaneously using
two parents!

2) When I edit the properties of a company, let's say moving from Japan to
Italy, how could I update Japan.companies_count and Italy.companies_count?
I tried to modify the update action of CompanieController:

###########################

  def update
    @company = Company.find(params[:id])
    @oldcountry=@company.country
    if @company.update_attributes(params[:company])
      @newcountry=@company.country
      @oldcountry.companies_count=@oldcountry.companies(:refresh).size
      @newcountry.companies_count=@newcountry.companies(:refresh).size
      flash[:notice] = 'Company was successfully updated.'
      redirect_to :action => 'show', :id => @company
    else
      render :action => 'edit'
    end
  end

###########################

but that did'nt work. :frowning:

3) Is there a Rails' way to cache the number of Employees directly in
Company.employees_count and Category.employees_count (i.e. through
a "has_many_many" relation)? That would prevent iterating through every
company of a given country/category to get its .employees_count!

Thanks a lot for your attention,

Have a good night,

Eric

Éric DUMINIL wrote:

I just would like to know: The right way to create a new company by updating
Country.find(id).companies.size is by doing
Country.find(id).companies.create . Right?

This is one way of doing it. Although if you have the id of the Country
you can just say:
  company = Company.create( :company_name=>'ABC Co', :country_id=>1 )
OR
  company = Company.new( :company_name=>'ABC Co', :country_id=>1 )
  company.save

Both of these will update the country countries_counter because you have
defined this in your belongs_to relationship.

1) But what if I'd like to keep in cache companies.size for the category in
which the new company is put? I can't create a company simultaneously using
two parents!

This can be solved using create or save on Company itself, rather then
doing Country.find.companies.create. Since you have defined the
belongs_to relationship and specified the counter cache ActiveRecord
will be smart enough to update both of them. The following example will
work:
  Company.create :company_name=>'ABC', :country_id=>1, :category_id=>1

2) When I edit the properties of a company, let's say moving from Japan to
Italy, how could I update Japan.companies_count and Italy.companies_count?

If you edit the properties of a company and save it ActiveRecord will
correctly increment/decrement the counter cache fields that you have
placed on your tables.

Using :refresh will ensure that you get the most recent data from the
database.

3) Is there a Rails' way to cache the number of Employees directly in
Company.employees_count and Category.employees_count (i.e. through
a "has_many_many" relation)? That would prevent iterating through every
company of a given country/category to get its .employees_count!

I don't know about the count via a another relationship, but you can
always count them at once rather then iterating over each company.

Category.count(
  :joins=>"INNER JOIN #{Company.table_name} ON " +
      "#{Company.table_name}.category_id=" +
      "#{Category.table_name}.id " +
      "INNER JOIN #{Employee.table_name} ON " +
      "#{Employee.table_name}.company_id=#{Company.table_name}.id",
  :group=>"#{Category.table_name}.id" )

This will give you an array of arrays, where each internal array is
employee_count, category_id:
  [ [ 4, 1 ], [ 2, 2 ], [ 30, 3 ] ]

It doesn't handle categories that do not have any employees although
that could be fixed.

This is perhaps not as simple of a solution you're looking for. Perhaps
someone else knows if you can do counter_cache using :through. :wink:

Zach

zdennis wrote:

This is one way of doing it. Although if you have the id of the Country
you can just say:
  company = Company.create( :company_name=>'ABC Co', :country_id=>1 )
OR
  company = Company.new( :company_name=>'ABC Co', :country_id=>1 )
  company.save

Both of these will update the country countries_counter because you have

                                        ^^^^^^^^^^^^^^^
should be companies_counter. :wink:

Zach