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.
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 have a library-like booking system. I want to make a form for adding books in stock, allowing the user to choose a book and choose a library (both are collection_select). Book has a many-to-many relationship with Library, through the stock_items table.
What I can't figure out is how can bring in the quantity, so that the user can add a number of instances of the same book to a chosen university. How should I approach implementing this quantity-type feature. It should create a chosen amount of records in the join table.
Here's my form (currently creates only 1 instance at a time):
<%= form_for(#item) do |f| %>
<%= f.label :choose_book %>
<%= f.collection_select(:book_id, Book.all, :id, :name, prompt: true) %>
<%= f.label :choose_library %>
<%= f.collection_select(:library_id, Library.all, :id, :name, prompt: true) %>
<%= f.submit "Add item in stock", class: "btn btn-info" %>
<% end %>
StockItem model
class StockItem < ActiveRecord::Base
belongs_to :library
belongs_to :book
has_many :bookings, foreign_key: :stock_id, dependent: :destroy
validates :availability, presence: true
validates :library_id, presence: true
end
Library model
class Library < ActiveRecord::Base
has_many :stock_items
has_many :books, through: :stock_items
end
Book model
class Book < ActiveRecord::Base
validates :year_of_publication, presence: true, length: { maximum: 4 }
validates :description, presence: true, length: { minimum: 10 }
validates :name, presence: true
has_many :stock_items, dependent: :destroy
has_many :libraries, through: :stock_items
has_many :contributions, dependent: :destroy
has_many :authors, through: :contributions
has_many :bookings, through: :stock_items
has_many :book_images, dependent: :destroy
accepts_nested_attributes_for :book_images
accepts_nested_attributes_for :authors
accepts_nested_attributes_for :libraries
accepts_nested_attributes_for :stock_items
accepts_nested_attributes_for :contributions
validates :name, presence: true
end
A bit of the StockItemsController
def create
#item = StockItem.new(item_params)
if #item.save
flash[:success] = "Item added to stock"
redirect_to stock_items_path
else
flash[:danger] = "Item has not been added to stock!"
render 'new'
end
end
def new
#item = StockItem.new
end
private
def item_params
params.require(:stock_item).permit(:library_id, :book_id, :availability)
end
I think your answer is systemic, rather than syntaxic, meaning you have to consider your system rather than the specific action syntax
--
M-to-M
You specifically need to look at the many-to-many association you've deployed here
Your population of the StockItem table is basically a way to create a collection for each library, and so you should look at the many collection based methods which ActiveRecord provides (specifically << and .delete)
Bottom line is that instead of creating new records for your StockItem model, I would just add to the collection of both library - giving you the files you need to make it work
--
Collection
As you're using has_many :through, you can add multiple instances of the same record to your collection (as opposed to has_and_belongs_to_many, which only permits single instances of records, as there is no primary_key)
This means you'll be able to just add books individually to the collection, and then use the sum method of SQL to calculate the quantity:
#app/controllers/stock_items_controller.rb
class StockItemsController < ApplicationController
def create
#library = Library.find params[:stock_item][:library_id]
#book = Book.find params[:stock_item][:book_id]
#library << #book
end
end
--
Quantity
This is opposed to the idea that you can add a qty attribute to your StockItem model, and then use the increment! method:
#app/controllers/stock_items_controller.rb
Class StockItemsController < ApplicationController
def create
#item = StockItem.find_by(library_id: params[:stock_item][:library_id],book_id: params[:stock_item][:book_id])
#item ? #item.increment!(:qty) : StockItem.create(stockitem_params)
end
end
I have an order form and when an order is created, a new customer is created as well. For this I have the following models:
class Customer < ActiveRecord::Base
has_many :orders
has_many :subscriptions, through: orders
end
class Order < ActiveRecord::Base
belongs_to :customer
has_many :subscriptions
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :subscriptions
end
class Subscription< ActiveRecord::Base
belongs_to :order
belongs_to :customer
end
On my order page I have this form:
= simple_form_for(#order) do |f|
= render 'order_fields', f: f
= f.simple_fields_for :subscriptions do |subscription|
= render 'subscription_fields', subscription: subscription
= f.simple_fields_for :customer do |customer|
= render 'customer_fields', customer: customer
= f.button :submit
In my OrderController I have:
def new
#order = Order.new
#order.build_customer
#order.subscriptions.build
end
def create
#order = Order.new(order_params)
if #order.save
(.... etc ...)
end
private
def order_params
params.require(:order).permit(
:amount,
customer_attributes: [ :id, :email, :password, :password_confirmation],
subscriptions_attributes: [ :id, :product_id, :customer_id])
end
Almost everything goes well:
- User is created
- Order is created and has customer_id = User.id
- Subscription is created and has order_id = Order.id
But somehow it wont associate the subscription to the customer :(
I keep having Subscription.customer_id = nil
Can someone please point me in the right direction? Is there something wrong in the models? or in the controllers? I have no idea anymore where to look.
Your relationships are set up a little different. Instead of creating a customer_id field on Subscription, I'd expect you'd just have a has_one :customer, through: :order.
If you do this you won't have need for a customer_id attribute on your Subscription model anymore. And if you want the id of the customer from the world-view of a subscription you'd call subscription.customer.id.
You may also want to add inverse_of designations for your relationships in your models (always a good practice to minimize reloading of models from the database).
So, in total, I'd recommend:
class Customer < ActiveRecord::Base
has_many :orders, inverse_of: :customer
has_many :subscriptions, through: orders
end
class Order < ActiveRecord::Base
belongs_to :customer, inverse_of: :orders
has_many :subscriptions, inverse_of: :order
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :subscriptions
end
class Subscription< ActiveRecord::Base
belongs_to :order, inverse_of: :subscriptions
has_one :customer, through: :order # THIS IS THE KEY CHANGE
end
Oh, and then you can remove the customer_id from the permitted attributes for subscriptions_attributes.
UPDATE
Given that Subscription#customer_id is meant to be disjointed from the Customer -> Order -> Subscription relationship... Ignore the above (except for perhaps the inverse_of stuff) and... You should be able to do:
class Customer < ActiveRecord::Base
has_many :subscriptions, through: :orders, after_add: :cache_customer_id
private
def cache_customer_id(subscription)
subscription.update_column(:customer_id, self.id)
end
end
Thanks pdobb! I got it working now with adding in the order.controller:
def create
#order = Order.new(order_params)
if #order.save
#order.subscriptions.each { subscription| subscription.update_column(:customer_id, #order.customer.id) }
end
I'm building an application where a user can build a lesson from an assortment of standards and questions to teach the standards, but I'm not exactly sure if I have set up everything correctly or not.
The 'new' page allows the user to use drop down menus to sort through to select the standards through the Lesson Controller
def new
#search = Standard.search(params[:q])
#standards = #search.result
#lesson = Lesson.new
end
def create
#lesson = current_user.selects.build(params[:lesson])
if #lesson.save
redirect_to edit_lesson_path(#lesson)
else
render :action => 'new'
end
end
def edit
#lesson = Lesson.find(params[:id])
#standards = #lesson.standards
end
Once the standards are selected, the user is redirected to the 'edit' page which shows each of the selected standards, but this is the part where I'm having trouble with and I'm not sure my models are set up correctly. There is a has_many through relationship between lessons and standards to select standards, and also a has_many through relationship between lessons and questions as well to select the questions associated with each standard.
I'm trying to list each of the questions associated with the standards underneath the parent standard, I have tried #questions = #standards.questions in the 'edit' method, but an ActiveRecord Relation NoMethod error is called. I have also tried #questions = Question.where(:standard_id => #standards) in the controller, but the page lists all of the questions for all of the selected standards underneath each standard.
My lesson model:
class Lesson < ActiveRecord::Base
attr_accessible :user_id, :name, :grade_id, :text_id, :date, :subject_id, :question_ids
has_many :select_standards
has_many :standards, through: :select_standards
has_many :select_questions
has_many :questions, through: :select_questions
end
Standard model:
class Standard < ActiveRecord::Base
attr_accessible :content, :grade_id, :subject_id
belongs_to :subject
belongs_to :grade
has_many :questions
end
Question model:
class Question < ActiveRecord::Base
attr_accessible :content, :standard_id
belongs_to :standard
has_many :select_questions
has_many :lessons, through: :select_questions
end
Select_standards:
class Selection < ActiveRecord::Base
attr_accessible :lesson_id, :standard_id
belongs_to :lesson
belongs_to :standard
end
The problem seems to be related to Rails not being able to figure out proper class names for your association names. For example, you've specified has_many :select_standards for your class Selection, by default Rails would search for SelectStandard class for association name :select_standards. The fix is easy in this case, which is to either change the association declaration to:
has_many :selections
or add class_name to association as:
has_many :select_standards, class_name: 'Selection'
You need to make sure that this is done for any and all the custom association names that do not match the actual ActiveRecord inherited class names.