11.1 user micropost -- a micropost model.
1. we will first generate a micropost model.
?
$ rails generate model Micropost content:string user_id:integer
?note, if you the content is longer, you can use type "text" instead of string
?
2. next, since we expect to retrieve all microposts associated with a user, we need to add index on user_id and created_at columns.
?
add_index :microposts, [:user_id, :created_at]
?
also, note this line
?
? ? ? t.timestamps
will add two columns to the table, created_at and updated_at.4. next, we will specify attr_accessible attrs.
we don't want user to edit the user_id attr through web, so we only add :content to attr_accessible
?
attr_accessible :content?
5. but this will create a difficulty for us, how to assign the user id when create a micropost?
go on with this question.
?
we will create user/micropost associations.
?
we start from test, TDD, yes.
?
describe Micropost do before(:each) do @user = Factory(:user) @attr = { :content => "value for content" } end it "should create a new instance given valid attributes" do @user.microposts.create!(@attr) end describe "user associations" do before(:each) do @micropost = @user.microposts.create!(@attr) end it "should have a user attribute" do @micropost.should respond_to(:user) end it "should have the right associated user" do @micropost.user_id.should == @user.id @micropost.user.should == @user end endend?
?
describe User do . . . describe "micropost associations" do before(:each) do @user = User.create(@attr) end it "should have a microposts attribute" do @user.should respond_to(:microposts) end endend?
?
6. all this test can pass after we add belongs_to and has_many methods to user.rb and micropost.rb.
?
after adding the two methods, we will have these methods for use:
?
micropost.userReturn the User object associated with the micropost.user.micropostsReturn an array of the user’s microposts.user.microposts.create(arg)Create a micropost (user_id = user.id).user.microposts.create!(arg)Create a micropost (exception on failure).user.microposts.build(arg)Return a new Micropost object (user_id = user.id).?
note the last method, it will return a new object that is not saved yet, but the user_id is already assigned.
?
7. ?to test the microposts that belong to a user, we need to factory some sample micropost records:
?
Factory.define :micropost do |micropost| micropost.content = "foo bar" micropost.association = :userend
?
then in the test code, we can
@mp1 = Factory(:micropost, :user => @user, :created_at => 1.day.ago) @mp2 = Factory(:micropost, :user => @user, :created_at => 1.hour.ago)
?
you can see, factory not only allow us to mass assign to bypass attr_accessible,
it also allow us to assign created_at and updated_at.
?
for normal ActiveRecord, it won't alow us to do so, rails magic will assign the timestamp automatically.
?
here is the test code in user_spec.rb:
?
describe "micropost associations" do before(:each) do @user = User.create(@attr) @mp1 = Factory(:micropost, :user => @user, :created_at => 1.day.ago) @mp2 = Factory(:micropost, :user => @user, :created_at => 1.hour.ago) end it "should have a microposts attribute" do @user.should respond_to :microposts end it "should have the right microposts in the right order" do @user.microposts.should == [@mp2, @mp1] end end?
to make it pass, we need to order the microposts:
?
default_scope :order => 'microposts.created_at DESC'
?this is the first time we encounter scope, we will get familiar later.
note, we use?
microposts.created_at
instead of
micropost.created_at
?
8. next, we will add test code to test that after destroying a user, the related micropost will be destroed automatically too.
?
it "should destroy associated microposts" do @user.destroy [@mp1, @mp2].each do |micropost| Micropost.find_by_id(micropost.id).should be_nil end end
?notes:
?
Micropost.find_by_id(micropost.id)
?will return nil if not found.
But
?
Micropost.find(micropost.id)
?will raise an exception?
?
lambda do Micropost.find(micropost.id)end.should raise_error(ActiveRecord::RecordNotFound)
?
9. the code to make the dependent destroy is very simple:
?
has_many :microposts, :dependent => :destroy
?
10. micropost validations:
again, start from test:
?
describe "validations" do it "should require a user id" do Micropost.new(@attr.should_not be_valid) end it "should require nonblank content" do @user.Microposts.build(:content => " ").should_not be_valid end it "reject long content" do @user.Microposts.build(:content => "a" * 141).should_not be_valid end end
?note, when contruct a new micropost object using associated user, we use
build()
instead of
Micropost.new
?
?