has_many through create relationship from existing models - ruby-on-rails

I have a marketplace where my users can create plans and their customers can join them. So I have a Plan model and a Customer model. The end goal is to subscribe a customer to a plan so I created a Subscription model and a has_many :through association but I need some help on getting the create working properly. A plan and a customer are already existing by the time the subscription is able to happen so I don't need to worry about creating the plan or customer on subscription#create, I just need to worry about joining the existing ones.
Where I'm at right now is I got the create working on the subscriptions model, but it's not associating to the correct customer. I need a Subscription model created for every customer that I subscribe to the plan and I'm using a multi select tag.
I'm using a has_many :through because a plan has many customers but a customer can also have many plans.
Please let me know if anything is not clear I tried to explain it as clearly and concisely as possible.
Plan Model
class Plan < ActiveRecord::Base
has_many :subscriptions
has_many :customers, through: :subscriptions
end
Customer Model
class Customer < ActiveRecord::Base
has_many :subscriptions
has_many :plans, through: :subscriptions, dependent: :delete_all
end
Subscription Model
class Subscription < ActiveRecord::Base
belongs_to :plan
belongs_to :customer
end
Routes.rb
resources :customers
resources :plans do
resources :subscriptions
end
Subscriptions Controller
class SubscriptionsController < ApplicationController
def new
#user = current_user
#company = #user.company
#plan = Plan.find(params[:plan_id])
#subscription = Subscription.new
end
def create
if #subscription = Subscription.create(plan_id: params[:subscription][:plan_id] )
#subscription.customer_id = params[:subscription][:customer_id]
#subscription.save
flash[:success] = "Successfully Added Customers to Plan"
redirect_to plan_path(params[:subscription][:plan_id])
else
flash[:danger] = "There was a problem adding your customers to this plan"
render :new
end
end
private
def subscription_params
params.require(:subscription).permit(:customer_id, :plan_id, :stripe_subscription_id)
end
end
Form:
<%= form_for [#plan, #subscription] do |f| %>
<%= f.hidden_field :plan_id, value: #plan.id %>
<div class="row">
<div class="col-md-6">
<%= f.select :customer_id, options_from_collection_for_select(#company.customers, 'id', 'customer_name', #plan.customers), {}, multiple: true, style: "width: 50%;" %><br />
</div>
<div class="col-md-12">
<%= f.submit "Add Customer To Plan", class: "btn btn-success pull-right" %>
</div>
</div>
<% end %>
params:
{"utf8"=>"✓",
"authenticity_token"=>"###",
"subscription"=>{"plan_id"=>"5", "customer_id"=>["", "153", "155"]},
"commit"=>"Add Customer To Plan",
"action"=>"create",
"controller"=>"subscriptions",
"plan_id"=>"5"}

params[:subscription][:customer_id] is an array:
"subscription"=>{"plan_id"=>"5", "customer_id"=>["", "153", "155"]},
Are you actually trying to set up a subscription between the plan and each of the customers in this array? If so try calling the update method for #plan object instead, passing these through in params[:plan][:customer_ids] (note the s)
EDIT:
When i said "pass through the ids in params[:plan][:customer_ids]" i was expecting you to do the standard controller behaviour for update, which is something along the lines of
#plan = Plan.find(params[:plan_id])
#plan.update_attributes(params[:plan])
if params = {:plan => {:customer_ids => [1,2,3]}} then the above code will be doing this:
#plan.update_attributes({:customer_ids => [1,2,3]})
which is like saying
#plan.customer_ids = [1,2,3]
#plan.save
When you set up an association, you get lots of methods you can call on the object. One of them is <association>_ids, in this case customer_ids, which is a way of setting the association: when you save, it will make the association between #plan and customers 1,2 & 3.
You were doing this:
#plan.customers << params[:plan][:customer_ids]
which is mixing up the customer records with the ids. If you're going to use push, aka <<, you need to push in customer objects, not ids. Just using customer_ids = is a quicker and simpler way of doing this.

Related

Creating X amount of new database rows using input X from a form in Rails with auto-incrementing name

I am working on my first rails app in which hotel owners can add types of rooms (premium, budget, single rooms, double rooms etc.) incl. the # of rooms of that type (room_count).
I am currently working with 3 tables,
(i) hotels table, where the user can create a new hotel,
(ii) accommodation_categories table, where the user can enter a new type of rooms incl. room_count &
(iii) an accommodations table, with the individual rooms per type.
My question is how to use the room_count input of the accommodation_categories table (obtained via a form) can be used to automatically create these room records in my accommodations table
e.g. how to translate the accommodation_category table input room_count into room_count*records of accommodations?
~ I am really sorry if this explanation is too elaborate, but I am not sure how else to explain it given that my technical vocabulary is rather limited ~
routes.rb
Rails.application.routes.draw do
resources :hotels do
resources :accommodation_categories do
resources :accommodations
end
end
hotel.rb
class Hotel < ApplicationRecord
has_many :accommodation_categories, dependent: :destroy
end
accommodation_category.rb
class AccommodationCategory < ApplicationRecord
belongs_to :hotel
has_many :accommodations, dependent: :destroy
end
accommodation.rb
class Accommodation < ApplicationRecord
belongs_to :accommodation_category
end
accommodation_categories_controller.rb
def new
#hotel = Hotel.find(params[:hotel_id])
#accommodation_category = AccommodationCategory.new
end
def create
#accommodation_category = AccommodationCategory.new(accommodation_category_params)
#hotel = Hotel.find(params[:hotel_id])
#accommodation_category.hotel = #hotel
#accommodation_category.save
redirect_to hotel_accommodation_categories_path
end
views/accommodation_categories/new.html.erb
<%= simple_form_for [#hotel, #accommodation_category] do |f|%>
<%= f.input :name %>
<%= f.input :room_count %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
I indeed used the cocoon gem with which you can create multiple nested objects via one form. It works perfectly.
To create multiple objects without details you can use controller lines such as:
#accommodation_category[:accommodation_count].times {#accommodation_category.accommodations.build}

Rails - How to create multiple "matches" in one action between a user and opportunities?

I'm looking for the easiest and the most clever way to create interest_id(match) in one-click.
Here is my MVC :
user.rb
class User < ApplicationRecord
has_many :interests, through: :opportunities
end
interest.rb
class Interest < ApplicationRecord
belongs_to :opportunity
belongs_to :user
end
opportunity.rb
class Opportunity < ApplicationRecord
has_many :interests
end
InterestsController.rb
def create
#user = current_user
#opportunities = Opportunity.all
#interest = Interest.new(interest_params)
if #interest.save!
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice:"it doesn't work"
end
end
def interest_params
params.permit(
:user_id,
:opportunity_id)
end
user/show
<%= link_to "Match", user_interests_path(#user), class:"btn btn-primary", :method => :post %>
For now, I can't pass opportunities (nil). Could you please advise me about the easiest way to create interests? (New on RoR for 6 months).
Many thanks for your help.
If I understand correctly your relation schema, the Interest is the join record associating a User to (eventually) many Opportunity, and vice-versa (many-to-many relationship).
With that being said (and please correct me if I am wrong), you can do the following to achieve what you want:
# in user/show
<% #opportunities.each do |opportunity| %>
<%=
link_to "Match opportunity #{opportunity.id}",
user_interests_path(#user, opportunity_id: opportunity.id),
class: "btn btn-primary",
method: :post
%>
<% end %>
# in interests_controller
def create
if current_user.interests.create(opportunity_id: opportunity_id_param)
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice: "it doesn't work"
end
end
private
def opportunity_id_param
params.require(:opportunity_id)
end
This suggested code:
requires the opportunity_id param for the interests#create action
use current_user to automatically set the user_id on the Interest model, so the end-users can't send a user_id that are not theirs (if they could, then each user could create interest for other users without their agreement... security flaw)
On a side note, I strongly advise you to not select all existing Opportunity record and display it on your page: it does not scale. Someday, you will end up with hundreds of Opportunity records, making this list too big from a User Experience perspective.
I suggest a smarter approach, for example some kind of ordering + limit: max of 10 records ordered by "most interest", which can be accomplished by the following:
# in controller
#popular_opportunities = Opportunity
.joins('LEFT JOINS interests ON interests.opportunity_id = opportunities.id')
.order('count(interests.*) DESC, opportunities.id')
.limit(10)
And then in the view, simply use #populator_opportunities instead of #opportunities.
Other options, like pagination, are also efficient in this case but IMO relevant ordering is the minimum.
First, you need to pass the ids of the opportunities you want to create interest some way, the best is a form, with checkboxes like MrShemek said, or a multi select dropdown.
I think you probably made some mistakes in User and Opportunity with the has_many and belong_to part:
class User < ApplicationRecord
has_many :interests
has_many :opportunities, through: :interests
# interest is the one that links user and opportunity, it has the references for both user and opportunities
end
class Opportunity < ApplicationRecord
has_many :interests
has_many :users, through: :interests
end
then in controller you could do
def create
#user = current_user
#opportunities = Opportunity.all
#user.opportunity_ids = interest_params[:opportunity_ids] # it will create the interrests automatically for the given ids (because the relations of has_many through)
if #user.save!
redirect_to user_interests_path, notice: 'it works'
else
render :new, notice:"it doesn't work"
end
end

Rails 4 self referential many to many relationships

I can't wrap my head around this any help would be appreciated. I went through many articles and other postings on here and I can't seem to get the results I'm looking for.
I have a User model and Team model.
Team Model has user_id and team_id
The user who creates the team will be user_id and users who are members of the team will be in the team_id
User Model
has_many :teams, foreign_key: :team_id
has_many :team_members, class_name: "Team", foreign_key: :user_id
Team Model
belongs_to :user, foreign_key: :team_id
belongs_to :team_member, class_name: "User", foreign_key: :user_id
The end result of what I'm trying to achieve is:
Each user can add as many team members
Each user can see a list of users who are part of their team.
A view where, Users who are part of a team can see which team they are part of.
I believe what you're looking for is a join table model. The issue is that both a User and a Team may have many of each other. So the relationship must be stored separately.
See this answer here on a similar question: https://stackoverflow.com/a/15442583/5113832
So you might choose a model structure of User, Team and TeamMembership.
Updated to destroy dependent team memberships when a user or team is destroyed.
#app/models/user.rb
class User < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :teams, :through => :team_memberships
end
#app/models/team.rb
class Team < ActiveRecord::Base
has_many :team_memberships, :dependent => :destroy
has_many :users, :through => :team_memberships
end
#app/models/team_membership.rb
class TeamMembership < ActiveRecord::Base
belongs_to :user
belongs_to :team
end
Updated to reply to question:
How would my controller look on create? (Adding a user to a team) – Michael
In order to add a user to to a team you COULD do it in UserController, you COULD do it in TeamController. However, because you are now creating a TeamMembership resource you would want to create a TeamMembership record in a TeamMembershipsController. This keeps with the "Rails" way of doing things. So for example:
# app/controllers/team_memberships_controller.rb
class TeamMembershipsController < ApplicationController
def index
#team_memberships = TeamMembership.all
end
def new
#team_membership = TeamMembership.new
end
def create
#team_membership = TeamMembership.new(team_membership_params)
if #team_membership.save
flash[:success] = 'Team membership created'
redirect_to team_memberships_path
else
flash[:error] = 'Team membership not created'
render :new
end
end
def destroy
#team_membership = TeamMembership.find_by_id(params[:id])
if #team_membership && #team_membership.destroy
flash[:success] = 'Team membership destroyed'
else
flash[:error] = 'Team membership not destroyed'
end
redirect_to team_memberships_path
end
private
def team_membership_params
params.require(:team_membership).permit(
:user_id,
:team_id
)
end
end
The advantage to having the TeamMembership resource is using this pattern to manage when a user is added (#create), or removed (#destroy) from a team.
The magic of Rails associations will take care of accessing a team's members (users) and a user's teams for each instance of those models.
You just go about your business doing CRUD on these resources and Rails takes care of the organization for you by your conforming to it's conventions.
Also I updated my original model code to destroy team memberships when a user or team is destroyed. This ensures no orphaned records are in your team_memberships table.
As a final note. You should also be able to easily use form_for to send a TeamMembership to your controller to be created. This could be done using select option dropdowns for users and teams with Rails' collection_select:
<%# app/views/team_memberships/new.html.erb %>
<h1>
Create Team Membership
</h1>
<%= form_for(#team_membership) do |f| %>
<%= f.label(:user) %>
<%= f.collection_select(
:user_id,
User.all,
:id,
:username
) %>
<%= f.label(:team) %>
<%= f.collection_select(
:team_id,
Team.all,
:id,
:name
) %>
<%= f.submit %>
<% end %>
The above code will render dropdown for all users and teams allowing you to select a specific combination to create a team membership from. Deleting a team membership is as easy as sending a DELETE #destroy request with the id of the team membership.
Another consideration might be adding a unique pair constraint to your database table and model within the migration and model validations.
Hope this helps!

has_many :through association - can't save record with a foreign id during create action

I have a Rails 4 application and am having problems creating a new record for a has_many :through association. A couple of observations that I made while debugging:
Commenting out the checkboxes associated with the Features model, the application will create and save the venue object properly.
The update action in the venues controller works fine for the feature checkboxes.
Can somebody tell me why my application is having problems saving the associated features object (an array) when creating a new venue? Not sure if this is because the foreign key, venue_id, doesn't exist prior to the save...
Here's the code:
venues.rb
class Venue < ActiveRecord::Base
has_many :venue_features, dependent: :destroy
has_many :features, :through => :venue_features
venue_features.rb
class VenueFeature < ActiveRecord::Base
belongs_to :venue
belongs_to :feature
features.rb
class Feature < ActiveRecord::Base
has_many :venue_features, dependent: :destroy
has_many :venues, :through => :venue_features
venues\new.html.erb (Features are listed as checkboxes - use selects relevant checkboxes)
<%= hidden_field_tag "venue[feature_ids][]", nil %>
<% Feature.all.each do |feature| %>
<div class="checkbox">
<label>
<%= check_box_tag "venue[feature_ids][]", feature.id, #venue.feature_ids.include?(feature.id) %>
<%= feature.name %><br>
</label>
</div>
<% end %>
venues_controller.rb
class VenuesController < ApplicationController
def create
#venue = Venue.new(venue_params)
if #venue.save(venue_params)
flash[:success] = "Success!"
redirect_to venues_path
else
flash[:error] = "Problem!"
render 'new'
end
end
def venue_params
params.require(:venue).permit(:attribute_1, :attribute_2, :feature_type_ids => [])
end
end
I'm sure there's a cleaner solution but what I ended up doing was to update a different set of strong parameters after a successful save. The problem prob has something to do with 1) one of my parameters, :feature_type_ids, is actually an array and/or 2) :feature_type_ids is in a different model (not the venues.rb). I thought that Rails would "automagically" handle saving to differnet models since venues.rb and features.rb have been set up through a :has_many :through relationship. I'd be curious for a better solution but the following works:
class VenuesController < ApplicationController
def create
...
if #venue.save(venue_only_params)
if #venue.update_attributes(venue_params)
flash[:success] = "Success!"
redirect_to venues_path
else
flash[:error] = "Problem!"
render 'new'
end
end
def venue_params
params.require(:venue).permit(:attribute_1, :attribute_2, :feature_type_ids => [])
end
def venue_only_params
params.require(:venue).permit(:attribute_1, :attribute_2)
end
end

Creating and designing checkboxes from an Array in Rails 4

I am struggling with trying to create and bootstrap design a simple survey in rails 4. A user (client) has many surveys through recordings. We intend to ask the user to fill out a completely new survey every so often so each client will have an archive of completed surveys. My models look like this:
class User < ActiveRecord::Base
has_many :recordings
has_many :surveys, through: recordings
#########
class Survey < ActiveRecord::Base
has_many :recordings
has_many :users, through: :recordings
#########
class Recording < ActiveRecord::Base
belongs_to :user
belongs_to :survey
Possible Issue #1: I decided that I did not need to separate the survey into survey has_many questions and questions has_many answers. The questions are static for each survey, so, my opinion was that each survey just needed standard column entries as if it were any other form.
So, with that said, I did something like this for the survey questions (sample survey with 1 question).
class Survey < ActiveRecord::Base
# ...
FAVORITE_FOODS = %w[pizza steak salad hotdogs pasta pancakes]
#....
def self.favorite_foods
FAVORITE_FOODS
end
Then in controller:
class Survey < ApplicationController
def new
#survey = current_user.surveys.build
#foods = Survey.favorite_foods
end
def create
#survey = current_user.surveys.build(survey_params)
if #survey.save
redirect_to surveys_path, notice: "Thank you for filling out our client survey."
else
flash[:notice] = "Your survey has not been completed."
render 'new'
end
end
#....standard crud for has_many
private
def survey_params
params.require(:survey).permit({:favorite_foods => []},....
end
Then finally on the view:
<%= form_for [#user, #survey] do |f| %>
<div class="control-group">
<div class="controls">
<h5>What are your favorite foods?</h5>
<div class="btn-group"><h6>
<% #foods.each do |food| %>
<button class="btn" type="button" data-toggle="button"><%= check_box_tag "food_array[]", food %> <%= food %></button>
<% end %></h6><%= f.submit%>
Question/Issue:
I have a functional problem and a design problem.
My current code is broken and clicking on the check_box and then submitting the form renders the form back to me.
The buttons have a check_box on them, but I want the buttons to serve as the checkboxes themselves.
I have watched this railscast: http://railscasts.com/episodes/52-update-through-checkboxes
but didn't think I needed to do an extra action in the controller for my case. Let me know.

Resources