I have a tutorial class and a step class. A tutorial has many steps and each step belongs to a tutorial. I have this setup in my models and in the routes file. On the show action of the tutorial class, all steps belonging to that tutorial are loaded as well. The problem is after creating several steps, 4-6, they will get out of order. Example, the first step loaded is step 7 but after that the steps are in order. I'm using postgresql for the database and I include t开发者_Python百科he pg gem in my gemfile. Tutorial model:
class Tutorial < ActiveRecord::Base
attr_accessible :name, :summary, :permalink
has_many :steps
validates :name, :presence => true,
:length => { :maximum => 50 },
:uniqueness => { :case_sensitive => false }
validates :summary, :presence => true,
:length => { :maximum => 2000 }
before_create :set_up_permalink
def to_param
permalink
end
private
def set_up_permalink
permalink = self.name.gsub(' ', '-').gsub(/[^a-zA-Z0-9\_\-\.]/, '')
self.permalink = permalink
end
Step model:
class Step < ActiveRecord::Base
attr_accessible :tutorial_id, :body, :position
belongs_to :tutorial
validates :body, :presence => true
before_create :assign_position
private
def assign_position
@tutorial = self.tutorial
@position = @tutorial.steps.size
@position = @position + 1
self.position = @position
end
end
Routes:
resources :tutorials do
resources :steps
end
def show
@tutorial = Tutorial.find_by_permalink(params[:id])
@steps = @tutorial.steps
@next = @steps[0]
@title = "#{@tutorial.name} - A Ruby on Rails tutorial"
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => @tutorial }
end
end
Tutorial show view
<%= render :partial => @tutorial.steps %>
Where are you setting the order? You could do it like this:
@steps = @tutorial.steps.order('position')
Or even better, since I can't think of any case where you would want the steps out of order:
In your Step model:
default_scope order('position')
Alternatively, you can define the order on the association definition:
In your Tutorial model:
has_many :steps, :order => 'position'
edit and just for the hell of it, here's a much more concise way to write your assign_position
method:
def assign_position
self.position = tutorial.steps.size + 1
end
Your logic for assigning position is flawed if steps can be arbitrarily added and removed.
E.g.
- I create a tutorial and 3 steps, with positions [1,2,3].
- Then I remove the step with position 2.
- Now there are 2 steps left with positions [1,3].
- Later I create another step, which is assigned position 3.
- Now there are three steps, but their positions are [1,3,3]. That's not right!
There are a couple approaches I would consider to rectify the situation:
First way
You can keep your code for assigning position to a new step, but when a step is removed from a tutorial then updated the positions to make sure that they are sequential. Here's a sketch of how that would work.
class Tutorial
def rectify_step_positions
# reassign positions so that all the steps form a sequence
# E.g. step positions [1,3,4] become [1,2,3]
end
end
class Step
after_destroy :trigger_rectify_positions
def trigger_rectify_positions
tutorial.rectify_step_positions
end
end
Second way
When you create a new step you can assign its position as 1 greater than the maximum position among all the existing steps.
def assign_position
self.position = tutorial.steps.order("position DESC").first.position + 1
end
This method is a little bit simpler to implement than the first. Of course now the positions don't have as much meaning since they are not necessarily sequential. The step with position 10 might be the 2nd step of the tutorial.
Conclusion
Once you have fixed your position problem then you can simply order the steps. Bricker has shown several ways to do that. Personally I like has_many :steps, :order => 'position'
精彩评论