I checked the Rails Guides about has_many through relationship but they have a poor documentation for has_many through's effects in controllers in views.
The models:
class Project < ActiveRecord::Base
has_many :twitter_memberships
has_many :twitter_accounts, through: :twitter_memberships
end
class TwitterAccount < ActiveRecord::Base
has_many :twitter_memberships
has_many :projects, through: :twitter_memberships
end
class TwitterMembership < ActiveRecord::Base
belongs_to :project
belongs_to :twitter_account
end
Routes:
resources :projects do
resources :twitter_accounts
end
The question is, when I use the create method , a TwitterMembership object is being created automatically but when I use new method and then the save method , TwitterMembership is not being created. Many of the posts in Stackoverflow is saying that create = new & save but it seems that they are not the same.
For example:
Project.first.twitter_accounts.create(name: "bar")
is creating both TwitterAccount and TwitterMembership:
SQL (0.5ms) INSERT INTO "twitter_accounts" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00], ["name", "test_record"], ["updated_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00]]
SQL (0.3ms) INSERT INTO "twitter_memberships" ("created_at", "project_id", "twitter_account_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00], ["project_id", 1], ["twitter_account_id", 8], ["updated_at", Thu, 20 Nov 2014 22:01:06 UTC +00:00]]
But when I do the same thing in the controllers/twitter_accounts_controller with new & create - it's not creating the TwitterMembership:
def new
@twitter_account = @project.twitter_accounts.new
end
def create
@twitter_account = @project.twitter_accounts.new(twitter_account_params)
@twitter_account.save
end
Can you explain me the difference between new-save and create? And is it okay if I use create method directly in my controller like this?
def create
@twitter_account = @project.twitter_accounts.create(twitter_account_params)
end
Thanks in advance.
Edit. Here is the full controller =>
class TwitterAccountsController < ApplicationController
before_action :set_project
before_action :set_twitter_account, only: [:show, :edit, :update, :destroy]
def new
@twitter_account = @project.twitter_accounts.new
end
def create
@twitter_account = @project.twitter_accounts.new(twitter_account_params)
@twitter_account.save
end
private
def set_project
@project = Project.friendly.find(params[:project_id])
end
def set_twitter_account
@twitter_account = TwitterAccount.friendly.find(params[:id])
end
def twitter_account_params
params.require(:twitter_account).permit(:name, :account_id)
end
end
You need to set your inverse relationships on your models to guarantee the build
and new
on associations will work consistently to setup the relationships, foreign keys, and intermediate associations:
class Project < ActiveRecord::Base
has_many :twitter_memberships, inverse_of: :project
has_many :twitter_accounts, through: :twitter_memberships
end
class TwitterMembership < ActiveRecord::Base
belongs_to :project, inverse_of: :twitter_memberships
belongs_to :twitter_account, inverse_of: :twitter_memberships
end
class TwitterAccount < ActiveRecord::Base
has_many :twitter_memberships, inverse_of: :twitter_account
has_many :projects, through: :twitter_memberships
end
Which should then make this work
@twitter_account = Project.first.twitter_accounts.new(name: 'baz')
@twitter_account.save
And voila:
SQL (0.3ms) INSERT INTO "twitter_accounts" ("created_at", "name", "updated_at") VALUES (?, ?, ?) [["created_at", "2014-11-21 19:19:06.323072"], ["name", "baz"], ["updated_at", "2014-11-21 19:19:06.323072"]]
SQL (0.1ms) INSERT INTO "twitter_memberships" ("created_at", "project_id", "twitter_account_id", "updated_at") VALUES (?, ?, ?, ?) [["created_at", "2014-11-21 19:19:06.324779"], ["project_id", 1], ["twitter_account_id", 7], ["updated_at", "2014-11-21 19:19:06.324779"]]
Also, using create
is perfectly acceptable assuming you've handled all your edge cases in the event of a failure. For example: if it fails do you show an error to the end user? If it is not supposed to fail, you should raise an exception (see create!
) to generate a notification of some sort to inform you of the failure.
In short, without the inverse_of
setup on has_many
, has_one
, and belongs_to
Rails doesn't completely understand the chain of intermediate models that glue things together. Rails will in certain cases (like create
) take a "best guess", and may get things right. Rails Guide doesn't make this clear, but the documentation on inverse_of spells this out.
It has become a Rails idiom to set inverse_of
on all has_many
, has_one
, and belongs_to
relationships, and should be done to ensure guessing by Rails is not necessary.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.