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
Related
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.
Is it un-RESTful/un-Ruby/bad practice to destroy objects in ruby on rails by doing a ".destroy" on an active record outside it's controller? For instance, when a user is destroyed I call destroy on all their posts from the User controller.
I would say yes, it is bad practice to do this. But mostly because there is a better way that rails has implemented for you. Like MrYoshiji pointed out in his comment, you should create an association between the user and a post.
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
Then when you delete a user, rails will automagically handle the destroys of the posts.
ActiveRecord
In reference to jkeuhlen's answer, I would not say it's outright bad practice to destroy an ActiveRecord object from a different controller, in principle.
In your case, it would not be very efficient to do it your way, but it wouldn't be against convention in my opinion:
As I understand, the MVC programming pattern basically means that if you call a controller, its primary function is to build a set of data depending on the users' input. This may, or may not, involve the controller's corresponding model
Although against the Resourceful principle directly, I don't see why it would be a problem to destroy data depending on various controller actions - if it doesn't involve the model, surely you'd be able to destroy all the relevant data from that controller?
--
Rails
As pointed out by jkeuhlen, Rails comes with a dependent: :destroy method to remove associative data upon record deletion.
The only thing to add to jkheuhlen's answer is as follows (taken from the answer):
class Post < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :posts, dependent: :destroy
end
This would mean that if you called the following, you need to make sure you're able to call the following:
#app/controllers/users_controller.rb
Class UsersController < ApplicationController
def destroy
#user = User.find params[:id]
#user.destroy -> will destroy all associated `posts`
end
end
I am trying to figure out the best way to accomplish my problem. I've got a pages table, and a user_types table. I am trying to specify multiple user types on a page. They will act as permission groups. I need to do this twice however. Once for read permissions, and once for edit permissions. Here is an example:
Home page has 3 user types that can read it - admin, super admin, public
It has 2 user types that can edit it - admin, super admin
I have one user_types table:
admin
super admin
public
etc
I have created two mapping tables (one for read, and one for edit):
pages_user_read_types
pages_user_edit_types
they both have page_id, and user_type_id
Is there a better way to accomplish this? If this is the best way, I need help figuring out the relationships for the models. I have this for one relationship
has_and_belongs_to_many :user_types, :join_table => :pages_user_read_types
How do i specify two relationships for seperate fields?
Thanks
The HABTM relationship in Rails has seemed to fall out of favor over the last couple of years with Rails developers to the has_many :through relationship. The only time you should use HABTM is when you have no need for any additional information about the relationship between two models. In your case, you are trying to emulate this by creating two HABTM relationships when you could effectively accomplish by having a join model with a editable attribute.
In code, it would look something like this:
class Page < ActiveRecord::Base
has_many :page_permissions
has_many :user_types, :through => page_permissions
def editable_user_types
page_permissions.includes(:user_types).where(:editable => true).map(&:user_type)
end
def read_only_user_types
page_permissions.includes(:user_types).where(:editable => false).map(&:user_type)
end
end
class PagePermission < ActiveRecord::Base
belongs_to :page
belongs_to :user_type
# When you create this model, you should have a boolean attribute for editable
end
class UserType < ActiveRecord::Base
has_many :page_permissions
has_many :pages, :through => :page_permissions
end
I think following this approach will allow you to consolidate to one join table which will be better in the future if you need to add additional attributes to the relationship (PagePermission) between Page and UserType.
At the very least, you probably want to add a Permission model. If it ever gets more complicated than what you've described, I would also recommend using CanCan.
class Permission < ActiveRecord::Base
#table is id, page_id, user_type_id, and permission_type (string).
belongs_to :page
belongs_to :user_type
end
In your controller, you can construct a filter chain like this:
class PagesController < ApplicationController
before_filter :load_page
before_filter :authorize_view!, only: [ :show ]
before_filter :authorize_edit!, only: [ :edit ]
def show
end
def edit
end
private
def load_page
#page = Page.find(params[:id])
end
def authorize_view!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "view").exists?
flash[:notice] = "You do not have permission to view that page."
redirect to root_path
end
end
def authorize_edit!
if !#page.permissions.where(user_type_id: current_user.user_type_id, permission_type: "edit").exists?
flash[:notice] = "You do not have permission to edit that page."
redirect to root_path
end
end
end
(This assumes you have a current_user method in your app).
I'm using Devise, and for each User account created I want to generate a relationship where:
class User < ActiveRecord::Base
belongs_to :business
end
class Business < ActiveRecord::Base
has_many :users
has_one :apt_setting
has_many :hours, :as => :hourable
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
end
So upon registration an associated Business object is created, and with each Business object an associated ApptSettings and BusinessHour object is created.
I currently have this implemented like this:
class Admin
before_create :create_associated_records
def create_associated_records
# create the associated business object
business = Business.create(:business_name => business_name, :subdomain => subdomain, :initial_plan => initial_plan)
# retrieve the id of the new business object
self.business_id = business.id
# create the associated records
BusinessHour.default_values(business_id)
ApptSetting.default_values(business_id)
end
end
class ApptSetting < ActiveRecord::Base
belongs_to :business
def self.default_values(business_id)
# ... create record with default values
end
end
class BusinessHour < Hour
belongs_to :hourable, :polymorphic => true
def self.default_values(business_id)
# ... create record with default values
end
end
This does work, but does it seem like the best design?
One alternative I'm considering is handling removing Admin -> create_associated_records, and instead do that work in Users::Accounts::RegistrationsController where I override the 'create' method. There I could build all the associated records, set :accepts_nested_attributes where appropriate, then call 'save' on the Business object, which should then cause all the associated records to be generated.
Thoughts on the best design, or any other ideas?
you don't need the default_values methods. In your create_associated_records you can change those calls to:
ApptSetting.create(:business_id => business_id)
Don't override the create method. before_create callbacks are a better way to go. In either case, If a business has many users, do you really want to create a new business every time a new user is created? How does a second user ever get added to a business? add something like,
def create_associated_records
return unless self.business_id.nil?
....
Also where are the business_name, subdomain, and initial_plan variables coming from in your method? Do you have them as attributes of the admin user? Seems like they should be only values of the business.
I think the biggest question here is, does a user really need a business in order to exist? Why can't the user just create their Business after they create their account?
** Edit: Being more clear / cleaner version using rails association methods:
class Admin
before_create :create_associated_records
private
def create_associated_records
return unless self.business_id.nil?
self.create_business
self.business.create_appt_setting
self.business.hours.create
end
end
I have two models with a many to many relationship using has_and_belongs_to_many. Like so:
class Competition < ActiveRecord::Base
has_and_belongs_to_many :teams
accepts_nested_attributes_for :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :competitions
accepts_nested_attributes_for :competitions
end
If we assume that I have already created several Competitions in the database, when I create a new Team, I would like to use a nested form to associate the new Team with any relevant Competitions.
It's at this point onwards that I really do need help (have been stuck on this for hours!) and I think my existing code has already gone about this the wrong way, but I'll show it just in case:
class TeamsController < ApplicationController
def new
#team = Team.new
#competitions.all
#competitions.size.times {#team.competitions.build}
end
def create
#team = Team.new params[:team]
if #team.save
# .. usual if logic on save
end
end
end
And the view... this is where I'm really stuck so I won't both posting my efforts so far. What I'd like it a list of checkboxes for each competition so that the user can just select which Competitions are appropriate, and leave unchecked those that aren't.
I'm really stuck with this one so appreciate any pointing in the right direction you can provide :)
The has_and_belongs_to_many method of joining models together is deprecated in favor of the new has_many ... :through approach. It is very difficult to manage the data stored in a has_and_belongs_to_many relationship, as there are no default methods provided by Rails, but the :through method is a first-class model and can be manipulated as such.
As it relates to your problem, you may want to solve it like this:
class Competition < ActiveRecord::Base
has_many :participating_teams
has_many :teams,
:through => :participating_teams,
:source => :team
end
class Team < ActiveRecord::Base
has_many :participating_teams
has_many :competitions,
:through => :participating_teams,
:source => :competition
end
class ParticipatingTeam < ActiveRecord::Base
belongs_to :competition
belongs_to :team
end
When it comes to creating the teams themselves, you should structure your form so that one of the parameters you receive is sent as an array. Typically this is done by specifying all the check-box fields to be the same name, such as 'competitions[]' and then set the value for each check-box to be the ID of the competition. Then the controller would look something like this:
class TeamsController < ApplicationController
before_filter :build_team, :only => [ :new, :create ]
def new
#competitions = Competitions.all
end
def create
#team.save!
# .. usual if logic on save
rescue ActiveRecord::RecordInvalid
new
render(:action => 'new')
end
protected
def build_team
# Set default empty hash if this is a new call, or a create call
# with missing params.
params[:team] ||= { }
# NOTE: HashWithIndifferentAccess requires keys to be deleted by String
# name not Symbol.
competition_ids = params[:team].delete('competitions')
#team = Team.new(params[:team])
#team.competitions = Competition.find_all_by_id(competition_ids)
end
end
Setting the status of checked or unchecked for each element in your check-box listing is done by something like:
checked = #team.competitions.include?(competition)
Where 'competition' is the one being iterated over.
You can easily add and remove items from your competitions listing, or simply re-assign the whole list and Rails will figure out the new relationships based on it. Your update method would not look that different from the new method, except that you'd be using update_attributes instead of new.