I'm still newbie in Rails, but got confused with the initialization of a HABTM association. Reading its documentation, it says
When initializing a new has_one or belongs_to association you must use the build_ prefix to build the association, rather than the association.build method that would be used for has_many or has_and_belongs_to_many associations.
So, basically, let's suppose we have two models:
class User < ApplicationRecord
has_and_belongs_to_many :organizations
end
class Organization < ApplicationRecord
has_and_belongs_to_many :users
end
Inside organization_controller, since I'm using Devise, my create method should have something like this:
#organization = current_user.organizations.build(organization_params)
#organization.save
However, it is not working. With byebug, I checked that for the current_user.organizations, the new organization was there, but, if I call #organization.users, there's an empty array. Looks like it's required to run current_user.save as well, is it correct? I was able to associate both models with this code:
#organization = Organization.new(organization_params)
#organization.users << current_user
#organization.save
You should highly consider using has_many, :through as that's the preferred way to do these kinds of relationships now in Rails.
having said that if you want to use has_and_belongs_to_many associations yes its get stored in join table on main object save.
Related
I’ve setup a has_many :through association between a User and Organisation model, using a Membership model as the join.
class Organisation < ActiveRecord::Base
has_many :memberships
has_many :users, :through => :memberships
end
class User < ActiveRecord::Base
. . .
has_many :memberships
has_many :organisations, :through => memberships
end
class Membership < ActiveRecord::Base
belongs_to :user
belongs_to :organisation
end
When a User creates an organisation, I want a membership to automatically be created linking the user to that organisation.
Where is the best place to attack this?
Options I’ve been investigating:
Use an after_create callback on the organisation
Move this process into a separate Ruby class.
In the organisations Controller, create action.
?
How would you recommend I go about it?
Is there somewhere in the Rails Guides where it outlines best practices for this kind of thing?
Rails 4.2.5.
#config/routes.rb
resources :organizations #-> url.com/organizations/new
#app/controllers/organizations_controller.rb
class OrganizationsController < ApplicationController
before_action :authenticate_user!
def new
#organization = current_user.organizations.new
end
def create
#organization = current_user.organizations.new organization_params
#organization.save
end
private
def organization_params
params.require(:organization).permit(:x, :y, :z) #-> membership automatically created
end
end
The above will automatically create the associated membership; assuming you're using Devise & have access to the current_user method.
--
The best practice is the most succinct; there is no way you're "meant" to do it.
One of the biggest fallacies I see in Rails is people trying to find the most acceptable way to do something (as if there's a rulebook). The best thing you can do is get it working then refactor the code.
As you progress through your app, you'll find that certain patterns can be changed, some removed and many combined. The more "DRY" you make your code, the better it is (as a rule).
My idea is way 3. Normaly, when set up many - many association in models, we should do creating temp table record auto through controller.
For example in controller you can write:
#organisation = current_user.organisations.build organisation_params
if #organisation.save
....
So that if #organisation is save then after that memberships record auto generate.
You can see this tutorial to see that:
http://blog.teamtreehouse.com/what-is-a-has_many-through-association-in-ruby-on-rails-treehouse-quick-tip
I think you should just be able to do something like:
org = Organisation.new
org.otherstuff = "populate other stuff"
org.users = [user_who_created]
org.save
After that the two should be related...? If you wanted to encapsulate this behavior you could do something like have a class method on Organization like create_org_for_user(name, user) and then do this logic in there, or you could do it in the controller action that handles the creation.
If there isn't any additional logic other than creating an organization and membership, I would just do #3. However, if you are planning on adding more logic for creating a new organization in the future, I would create a new service (#2).
Where is the best place to attack this?
I would like to say you should write this in OrganisationsController's create action to make a DRY on update action as well(use strong parameter) .Because form attribute that you are getting is from outer worlds and it is best to use permit method on required params using Strong Parametre concept.
def create
#organisation = Organisation.new(organisation_params)
...
end
def organisation_params
# here you could write all the params which you want to permit from outer worlds
end
Nore more info about Strong Parameters
I have the following models
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validates_presence_of :category_ids
end
class Category < ActiveRecord::Base
has_and_belongs_to_many :businesses
end
I am setting the relationship through the business creation form, using the category_ids attribute.
I tried using validates_presence_of, however, this is not validating the existence of a category.
I can manipulate the form through the browser, give a non-existing ID for a category. After submitting the form, I get an error:
Couldn't find Category with id=181723
Edit:
Added the following custom validation method, but I am still getting the same error, as if the validation was not being run.
class Business < ActiveRecord::Base
has_and_belongs_to_many :categories
validate :categories_exist
def categories_exist
category_ids.each do |c|
errors.add(:category_ids, :category_doesnt_exist) unless Category.exists? c
end
end
end
There's probably a variety of ways you could achieve this but I'd recommend looking at Custom Validations and ActiveRecord Callbacks.
You can check out the validates_existence gem. This gem has been very useful to me for validating if foreign keys correspond to legitimate parent records. As described in the readme:
This plugin library adds ActiveRecord models a way to check if a
:belongs_to association actually exists upon saving.
class Company
has_and_belongs_to_many :users
end
class User
has_and_belongs_to_many :companies
end
when i delete a company, what's the best (recommended) way to delete ONLY the associations of the users from that company? (i mean not the actual users, only the associations)
I prefer the following since it keeps model logic in the model. I don't understand why ActiveRecord doesn't just do it. Anyway, in both joined models, I add the following callback.
before_destroy {|object| object.collection.clear}
So in your example:
class Company
has_and_belongs_to_many :users
before_destroy {|company| company.users.clear}
end
class User
has_and_belongs_to_many :companies
before_destroy {|user| user.companies.clear}
end
In a lot of discussions around doing a cascade delete on a collection association, many people declare the HABTM association dead and recommend has_many :through instead. I disagree. Use whatever makes sense. If the association has no intrinsic attributes, then use HABTM.
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_and_belongs_to_many
collection.delete will do the trick.
If you call destroy instead of delete, associations will be deleted automatically.
I have a simple has_many through relationship set up:
class Tag < ActiveRecord::Base
has_many :profile_tags
has_many :profiles, :through => :profile_tags
end
class ProfileTags < ActiveRecord::Base
belongs_to :profile
belongs_to :tag
end
class Profile < ActiveRecord::Base
has_many :profile_tags
has_many :tags, :through => :profile_tags
end
From my view I am accepting a set of tags (just strings), and am iterating over them in my controller and calling Tag.create( ... ) on each of them, and pushing them into an array. This all works fine.
So I get to a point where I have an Array of Tag objects (tags) which were each returned by the call to create, and variable #profile which was created by doing Profile.new
I would like to do: #profile.tags = tags
Doing this causes this error on the line where I try the assignment:
uninitialized constant Profile::ProfileTag
Rails is acting like I need to manually create and assign the join table association, even though here http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association it states that when you do an assignment like this, new associations will be created and if some are gone they will be deleted.
Any ideas what I could be doing wrong here?
Rails assumes that model classes are named with the singular form, i.e. the class ProfileTags should be called ProfileTag.
Depending on which Rails version you are using, probably the easiest way to fix this is to re-create the model using script/destroy and script/generate in Rails 2.x or rails destroy and rails generate in Rails 3.
Alternatively, specifying the class name manually by adding :class_name => 'ProfileTags' to the has_many declarations should work too.
Hi (huge Rails newbie here), I have the following models:
class Shop < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :title, :user_id, :message => "is already being used"
end
and
class User < ActiveRecord::Base
has_one :shop, :dependent => :destroy
end
When I'm about to create a new shop, I get the following error:
private method `create' called for nil:NilClass
This is my controller:
#user = current_user
#shop = #user.shop.create(params[:shop])
I've tried different variations by reading guides and tutorials here and there, but I'm more confused than before and can't get it to work. Any help would be greatly appreciated.
A more concise way to do this is with:
#user.create_shop(params[:shop])
See methods added by has_one in the Ruby on Rails guides.
First of all, here is how to do what you want:
#user = current_user
#shop = Shop.create(params[:shop])
#user.shop = #shop
Now here's why your version did not work:
You probably thought that this might work because if User had a has_many relation to Shop, #user.shops.create(params[:shop]) would work. However there is a big difference between has_many relations and has_one relations:
With a has_many relation, shops returns an ActiveRecord collection object, which has methods that you can use to add and remove shops to/from a user. One of those methods is create, which creates a new shop and adds it to the user.
With a has_one relation, you don't get back such a collection object, but simply the Shop object that belongs to the user - or nil if the user doesn't have a shop yet. Since neither Shop objects nor nil have a create method, you can't use create this way with has_one relations.
Two more ways if you want save instead of create:
shop = #user.build_shop
shop.save
shop = Show.new
shop.user = #user
shop.save
Just to add to above answers -
#user.create_shop(params[:shop])
Above syntax creates new record but it subsequently deletes similar existing record.
Alternatively, if you do not want to trigger delete callback
Shop.create(user_id: user.id, title: 'Some unique title')
This thread might be helpful. Click here