I have a User and Group model that I bind with an Assignments model. A user has_many groups and a group has_many users. However, when I create a new group, when I do Group.last.users.count the output is 0 or ActiveRecord::Associations::CollectionProxy []. Do I need to change an aspect of my simple_form? Did I not build the middleman model correctly to bind users and groups? I would like it so that when a user creates a group, the group_ids are in the array when doing User.last.groups, etc. When I had it as User has_many groups and a Group belongs_to user, the group ids would be tied to a user. But since switching to has_many for both models and introducing the Assignments model, this is no longer the case.
Group Model
class Group < ActiveRecord::Base
validates :user_id, presence: true
has_many :assignments
has_many :users, through: :assignments
has_many :posts
has_many :attachments
has_secure_token
end
User Model
class User < ActiveRecord::Base
...
has_many :assignments
has_many :groups, through: :assignments
accepts_nested_attributes_for :assignments
...
Assignment Model
class Assignment < ActiveRecord::Base
belongs_to :group
belongs_to :user
accepts_nested_attributes_for :group
accepts_nested_attributes_for :user
end
Groups Controller
class GroupsController < ApplicationController
before_action :authenticate_user!
def new
#group = current_user.groups.build
end
def create
#group = current_user.groups.build(group_params)
#group.user_id = current_user.id
if #group.save
redirect_to groups_path
else
render :new
end
end
private
def group_params
params.require(:group).permit(:group_name, :description, :user_id)
end
end
Group new.html.erb
<%= simple_form_for #group do |f| %>
<%= f.input :group_name %>
<%= f.text_area :description %>
<%= f.button :submit %>
<% end %>
You can do something like that:
I guess the build method seems not saving the joiner model you can use new. There is an issue here:
As there is many-to-many relation between Group and User. Thus there is no need for user_id in group. Thus the validation is unnecessary. And it causes the problem with my first approach. In model comment the validation. # validates :user_id, presence: true
def create
#group = current_user.groups.new(group_params)
# #group.user_id = current_user.id // this line is unnecessary
if #group.save
redirect_to groups_path
else
render :new
end
end
Or, (I'll not suggest it as it is not a good approach)
def create
#group = Group.new(group_params)
if #group.save
current_user.groups << #group
redirect_to groups_path
else
render :new
end
end
Related
I was wondering if someone could help me out with an application that has some ecommerce characteristics.
Context: Via the application a bike shop chain ('chains') can rent out
bikes ('bikes'),
by picking out a bike type such as mountainbike, city bike etc. ('bike_types) and
bike options, such as helmets etc. ('bike_options')
which are dependent on the individual bike store ('bike_stores')
this rental of the bikes & options will all be captured in an order ('orders')
the relationship between orders and bikes is many-to-many, therefore I created a table to bridge this ('order_bikes')
Final notes:
Before the rental process, the chain owner first created his/her (i) bike_stores, (ii) bike_types, (iii) bikes and (iv) bike_options, this part of the application is working. Therefore, he/she only needs to select bike_types/bikes/options out of the existing inventory previously created.
I limit the scope of the question by leaving out the bike_options, this was mainly to provide some context in order to understand the db schema build up.
Error message: Unpermitted parameter: :bike_id
Code:
models
class Order < ApplicationRecord
belongs_to :bike_store
has_many :bike_types, through: :bike_store
has_many :order_bikes, inverse_of: :order, dependent: :destroy
accepts_nested_attributes_for :order_bikes, allow_destroy: true
end
class OrderBike < ApplicationRecord
belongs_to :bike
belongs_to :order
accepts_nested_attributes_for :bike
end
class Bike < ApplicationRecord
belongs_to :bike_type
validates :name, presence: true
has_many :order_bikes
has_many :orders, through: :order_bikes
end
class BikeType < ApplicationRecord
belongs_to :bike_store
has_many :bikes, dependent: :destroy
accepts_nested_attributes_for :bikes, allow_destroy: true
has_many :bike_options, dependent: :destroy
accepts_nested_attributes_for :bike_options, allow_destroy: true
validates :name, :bike_count, presence: true
end
class BikeStore < ApplicationRecord
has_many :bike_types, dependent: :destroy
has_many :orders, dependent: :destroy
end
Order controller
class OrdersController < ApplicationController
def new
#bike_store = BikeStore.find(params[:bike_store_id])
#order = Order.new
#order.order_bikes.build
#bike_type_list = #bike_store.bike_types
end
def create
#order = Order.new(order_params)
#bike_store = BikeStore.find(params[:bike_store_id])
#order.bike_store = #bike_store
#order.save
redirect_to root_path
end
private
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
end
view
<%= simple_form_for [#bike_store, #order] do |f|%>
<%= f.simple_fields_for :order_bikes do |order_bike| %>
<%= order_bike.input :bike_quantity %>
<%= order_bike.association :bike %>
<% end %>
<%= f.input :arrival %>
<%= f.input :departure %>
<%= f.submit %>
<% end %>
If you check coed from simple form here, you will see what actually method association does.
def association(association, options = {}, &block)
# ... simple form code here ...
attribute = build_association_attribute(reflection, association, options)
input(attribute, options.merge(reflection: reflection))
end
We are interested in build_association_attribute method call. here
def build_association_attribute(reflection, association, options)
case reflection.macro
when :belongs_to
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
# ... the rest of code ...
end
end
Your order bike model has belongs_to :bike association. So when you call order_bike.association :bike it builds :bike_id attribute in your form. If you check params hash that comes to your controller, I believe you'll see that attribute coming from your view.
I added bike_id to permitted parameters. I hope it will fix your problem..
def order_params
params.require(:order).permit(:arrival, :departure,
order_bikes_attributes: [:id, :bike_id, :bike_quantity, :_destroy,
bikes_attributes: [:id, :name,
bike_types_attributes: [:id, :name]]])
end
So I have two models. One is Meal and the second is called Food. A meal can have multiple food items and a food item can be in multiple meals. Basically a many-to-many association. I did that with a has_many through association. The association model is called MealFood.
I want to make a form so that when I'm creating a meal I have the option to select as many food items as I want either through a checkbox or a select option field.
How can I achieve that? I'm very new to Rails so I don't even know where to begin. Any help would be amazing! Thanks in advance!
Here's my code so far:
class Meal < ApplicationRecord
belongs_to :user
has_many :meal_foods
has_many :foods, through: :meal_foods
end
class Food < ApplicationRecord
has_many :meal_foods
has_many :meals, through: :meal_foods
end
class MealFood < ApplicationRecord
belongs_to :meal
belongs_to :food
end
Meals Controller
def index
end
def new
#meal = Meal.new
#meal.foods.new
end
def create
#meal = Meal.new(meal_params)
if #meal.save
redirect_to #meal
else
render 'new'
end
end
private
def meal_params
params.require(:meal).permit(:meal_type, :date, :total_calories, :total_carbohydrates, :total_fat, foods_attributes: [:name, :calories, :proteins, :carbohydrates, :fat])
end
You don't need nested attributes to assign associations. All you really need is checkboxes or a select:
<%= form_with model: #meal do |f| %>
# ...
<%= f.collection_select(:foods_ids, Food.all, :id, :name) %>
<%= f.submit %>
<% end %>
You then need to whitelist the attribute foods_ids attribute:
def food_params
params.require(:food).permit(:foo, :bar, foods_ids: [])
end
This works as every many to many association has a _ids setter that takes an array of ids and replaces (or creates) the associations as needed.
Nested attributes is only needed if you need pass attributes for the associated records.
I'm running into an issue with an associated model. I have a nested attributes for my user, to a reviewer. A user can essentially review another person, thus be a reviewer and be the person reviewed.
It's set up like this:
# User
has_many :reviewers
accepts_nested_attributes_for :reviewers
has_many :active_managements, class_name: 'Reviewer',
foreign_key: 'reviewer_id',
dependent: :destroy
class Reviewer < ActiveRecord::Base
belongs_to :user
belongs_to :reviewer_id, class_name: 'User'
end
now in my users controller I have:
class UsersController < ApplicationController
def edit
#user = User.find(params[:id])
#user.reviewers.build
redirect_to root_url && return unless #user.activated?
end
def update
#user = User.find(params[:id])
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user)
else
render 'edit'
end
end
private
def user_params
params.require(:user).permit(:invitation_token, :first_name, :admin,
:last_name, :title, :email, :password,
reviewers_attributes: [:reviewer_id])
end
the error that I get is:
User(#70197180889680) expected, got String(#70197172430700)
happening on "user_params", so I assume it has to do with my attributes. Anybody know what's up?
The line belongs_to :reviewer_id, class_name: 'User' is incorrect. Try changing it to something like belongs_to :reviewing_user, class_name: 'User' (replacing reviewing_user with whatever name you want to use for this association :), not field name.
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to
The immediate fix is here:
#app/models/review.rb
class Reviewer < ActiveRecord::Base
belongs_to :reviewer
end
--
This is how I'd fix the systemic issue:
#config/routes.rb
resources :users do
resources :reviewers #-> url.com/users/:user_id/reviews/new
end
#app/controllers/reviewers_controller.rb
class ReviewersController < ApplicationController
def new
#user = User.find params[:user_id]
#review = user.reviewers.new
end
def create
#user = User.find params[:user_id]
#review = user.reviewers.new reviewer_params
end
private
def review_params
params.require(:reviewer).permit(:user_id, :reviewer_id)
end
end
Models
Apart from this, I think your main issue is the way your models are set up.
Ideally, you want to have reviewer and user as the same data-set (I presume they're both users), which makes your current setup really inefficient...
#app/models/user.rb
class User < ActiveRecord::Base
has_and_belongs_to_many :reviewers,
class_name: "User",
join_table: :reviewers_users
foreign_key: :user_id,
association_foreign_key: :reviewer_id
end
What you're looking for is something called a self referrential association, which basically allows you to associate the same model in a many-to-many relationship.
In most cases, this will be used with a has_and_belongs_to_many relationship; you could also use a has_many :through although it's not as common.
The above will allow you to use the following:
#user = User.find params[:id]
#user.reviewers #-> collection of users who are reviewers of original user
I am attempting to build a table to handle both the location and category a certain campaign has been set to with the following model associations:
class Campaign < ActiveRecord::Base
has_many :campaign_category_metro_bids, dependent: :destroy
has_many :metros, through: :campaign_category_metro_bids
has_many :categories, through: :campaign_category_metro_bids
end
class Metro < ActiveRecord::Base
has_many :campaign_category_metro_bids
has_many :campaigns, through: :campaign_category_metro_bids
has_many :categories, through: :campaign_category_metro_bids
end
class Category < ActiveRecord::Base
has_many :campaign_category_metro_bids
has_many :campaigns, through: :campaign_category_metro_bids
has_many :metros, through: :campaign_category_metro_bids
end
class CampaignCategoryMetroBid < ActiveRecord::Base
belongs_to :campaign
belongs_to :category
belongs_to :metro
end
When attempting to create a campaign for selecting two different cities and categories the result is NULL for the id of one of the paramters as:
Campaign creation code:
def new
if signed_in?
# create new campaign
#user = User.find(params[:id])
#campaign = #user.campaigns.new
else
redirect_to signin_path
end
end
def create
#campaign = User.find(params["campaign"]["user_id"]).campaigns.build(campaign_params)
if #campaign.save
flash[:success] = "Campaign created!"
redirect_to current_user
else
render 'new'
end
end
UPDATED
The view to create the campaign uses two separate collection_select for Category and Metro as:
<%= f.collection_select :category_ids, Category.all, :id, :display_category, {}, {multiple: true} %>
and
<%= f.collection_select :metro_ids, Metro.all, :id, :full_name, {}, {multiple: true} %>
campaigns_params:
def campaign_params
params.require(:campaign).permit(:name, :campaign_category_metro_bid_id,
:metro_ids => [], :category_ids => [])
end
Is there a better way to allow for the creation of a 3 table relation as I am attempting?
or a way to link the Category and Metro models at selection so that the resultant table is something like below upon campaign creation:
I think the problem may be the multi select that you have on categories and metros. You're essentially trying to fit multiple foreign_keys for the same reference into a single row record. If category ID and metro ID are both defined as integers, you would need to create multiple records in order to be able to save this.
You would need to add some logic to see if your selection params have a length of > 1 and based on that you'll need to create and save a new row. The logic would look something like this
params[:category_ids].each do |category|
params[:metro_ids].each do |metro|
#user.campaign.create(category_id: category, metro_id:metro) #any other params would go here too
end
end
This would essentially loop through your multi-select to create a new record for each combination.
The joiner model is not being saved when I try this (assumingAccount has_many :users, through: :roles and vice-versa):
def new
#account = current_user.accounts.build
end
def create
#account = current_user.accounts.build(params[:account])
#account.save # does not save the joiner model
end
That should create #account, and a Role record where user_id=current_user.id and account_id: #account.id. Only #account gets saved. There are not records in the Role model. The results are consistent using console.
Replace current_user.accounts.build with current_user.accounts.create in the create action, the joiner (role record) model gets saved. For this reason I don't think this is a validation issue. I'm using Rails 3.2.3.
Models:
class User < ActiveRecord::Base
has_many :roles
has_many :accounts, through: :roles
end
class Account < ActiveRecord::Base
has_many :roles
has_many :users, through: :roles
accepts_nested_attributes_for :users
end
class Role < ActiveRecord::Base
attr_accessible
belongs_to :users
belongs_to :accounts
end
View
<%= simple_form_for(#account) do |f| %>
<%= render 'account_fields', f: f %>
<%= f.submit %>
<% end %>
Try to use
UPDATED:
class User < ActiveRecord::Base
has_many :roles
has_many :accounts, through: :roles, :autosave => true
end
You can find more info about autosave here.
or using a callback in User model
after_save :save_accounts, :if => lambda { |u| u.accounts }
def save_accounts
self.accounts.save
end
This is a bug: https://github.com/rails/rails/issues/6161#issuecomment-5631018