문제

Say I have a Statement model, which has_many :months. There are always 12 months associated with a statement, but the first month can vary (eg months = [Mar, Apr, May...Jan, Feb])

Given a certain month, what's the MVC way to find the previous month?

I find myself accessing it through the statement which feels dirty:

# statement.rb
has_many :months

def previous_month(month)
  if months.index(month) == 0
    return nil
  else
    return months[months.index(month) - 1]
  end
end

# blergh
prev_month = month.statement.previous_month(month)

Should I have a previous_month_id column in my database? How would you implement this functionality? I'm using Rails 2.3.x.

도움이 되었습니까?

해결책

I would define it on the Month model to cut back on the roundtrips.

# month.rb
class Month < ActiveRecord::Base
  belongs_to :statement, :include => :months

  def previous
    return nil if self.index == 0
    find_or_create_by_index_and_statement_id(self.index - 1, self.statement.id)
  end

  def index
    statement.months.index self
  end
end

so that you can get june.previous. This should even work on unsaved records.

다른 팁

How are these months added? If they're added separately in the chronological order of the months then you can simply do what you have but you should define the order in the relationship.

#statement.rb

has_many :months, :order => 'created_at ASC'

If they're added some other way then you might need to think about having an order column and using acts_as_list to maintain the order.

To do this the MVC way, I would probably push this logic on the thing that 'owns' the statements. After all a statement usually belongs to something. After reading the comments though, it sounds like this is an inherited project. If not you have to ask why would you have a 'months' relationship, when the statement has a created_at column you can tie into? Here is what i came up with, it may not be helpful to you. Although do checkout Date::MONTHNAMES at the very least it sounds like that might be helpful to you.

describe User do
  before(:each) do
    @user = User.create!
  end

  it "should know about months" do
    Statement.create(:user => @user)
    @user.statements.last.month_name.should == "November"
  end

  it "should report last months statement as nil when there is no statement" do
    @user.last_months_statement.should be_nil
  end

  it "should report last months statement as nil if there is only one for this month" do
    Statement.create(:user => @user)
    @user.last_months_statement.should be_nil
  end

  it "should report a statement from the previous month if there is one"  do
    target = Statement.create(:user => @user, :created_at => 1.month.ago)
    Statement.create(:user => @user)
    @user.last_months_statement.should == target
  end

  it "should report last months statement if there a several" do
    Statement.create(:user => @user, :created_at => 1.month.ago)
    Statement.create(:user => @user)
    Statement.create(:user => @user, :created_at => 2.months.ago)
    @user.last_months_statement.month_name.should == "October"
  end
end

class User < ActiveRecord::Base
  has_many :statements, :order => "created_at"

  def last_months_statement
    if statements.size <= 1 || statements.last.created_at.month < Time.now.month
      nil
    else
      index = statements.index(statements.last)
      statements[index - 1]
    end
  end
end

class Statement < ActiveRecord::Base
  belongs_to :user

  def month
    created_at.month
  end

  def month_name
    Date::MONTHNAMES[created_at.month]
  end
end
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top