Rails - has_many :through association + save data for association - ruby-on-rails

I have setup has_many and has_many :through association between a Order,User,Product and Order_detail model as a join table.
Models:
class Order < ActiveRecord::Base
has_many :order_details
belongs_to :user
has_many :products, through: :order_details
end
class OrderDetail < ActiveRecord::Base
belongs_to :order
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :order_details
has_many :orders, through: :order_details
end
class User < ActiveRecord::Base
has_many :orders
end
How to save automatically for join table order_details.
Now data save only to order table.
Need to save all products to order_tables for current order and user
class OrdersController < ApplicationController
before_action :authenticate_user!
def index
#order = Order.all
end
def new
# #order = Order.new
#order = current_user.orders.new
end
def create
#order = current_user.orders.new(order_params)
#order.date = Date.today.to_s
if #order.save
# i think this is bad wrong to implementation of functional)
# params[:product_id].each do |detail_product_id|
# #order.order_details.product_id = detail_product_id
# #order.order_details.user_id = current_user
# #order.order_details.save
flash[:success] = "Order was successfully submitted"
redirect_to new_order_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:date, :product_id => [])
end
end
My schema:
create_table "order_details", force: true do |t|
t.integer "order_id"
t.integer "product_id"
t.integer "quantity"
t.integer "price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "orders", force: true do |t|
t.integer "user_id"
t.date "date"
end
add_index "orders", ["user_id"], name: "index_orders_on_user_id", using: :btree
create_table "orders_products", id: false, force: true do |t|
t.integer "order_id"
t.integer "product_id"
end
create_table "products", force: true do |t|
t.string "product_name"
t.integer "product_price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "available_status"
t.string "product_type"
end

In your view, add fields for order_details like:
<%= f.fields_for :order_details do |od| %>
<%= od.label 'your attribute for OrderDetail' %>
<%= # od.text_field 'your attribute' %>
<% end %>
Then in your model, accept nested attributes for order_details
like:
accepts_nested_attributes_for :order_details
These are sample values, you can use this logic with your actual attributes.
In your controller, permit attributes for order_details like:
params.require(:order).permit(:id, :name, order_details: [
#attributes of order details
])

Assuming that product_ids is an array of the product ids that you wish to add to the order, you could try assigning them in the following way and Rails should automagically create those association records for order_details when you then call #order.save
#order.products << Product.find_by_id(product_ids)

Am i right add that rows for controller?
Order_controller:
def create
#order = current_user.orders.new(order_params)
#order.products = Product.find_by_id(order_params[:product_ids])
#order.date = Date.today.to_s
if #order.save
flash[:success] = "Order was successfully submitted"
redirect_to new_order_path
else
render :new
end
end
private
def order_params
params.require(:order).permit(:date, order_details: [:product_id])
end
end
order model:
accepts_nested_attributes_for :order_details
I'm trying to save from 3 different types of products.
But how to take one product id from each part ? Because now I can choose only one product from all
View:
= simple_form_for(#order, html: {:class => 'well form-horizontal', :method => :post, :action=> :create }) do |f|
.col-xs-12.col-sm-6.col-md-8
= render 'shared/error_messages', object: f.object
%br
= simple_fields_for :order_details do |od|
= od.collection_radio_buttons :product_ids, Product.get_first_course, :id, :product_name ,:item_wrapper_class => 'inline'
%hr
= od.collection_radio_buttons :product_ids, Product.get_main_course, :id, :product_name, :item_wrapper_class => 'inline'
%hr
= od.collection_radio_buttons :product_ids, Product.get_drink, :id, :product_name,:item_wrapper_class => 'inline'
%hr
= f.button :submit, class: "btn btn-primary"

I change my association type to HABTM and that's enough for my situation. So..
models:
class Order < ActiveRecord::Base
has_and_belongs_to_many :products
before_destroy { products.clear }
end
class Product < ActiveRecord::Base
has_and_belongs_to_many :orders
end
Order_controller:
def create
order = current_user.orders.new(date: Date.today.to_s)
#order_products = Product.where(id: order_params[:product_ids])
order.products << #order_products
if order.save
#blalblal - sucsess
else
#blabla - alert-notice
end

Related

Ruby Shopping cart cant save user

I am working on creating shop cart / order. I created shop cart with association by this guide so I have some problem. After a new sign in session My app can't find my previous cart and create one new
module ApplicationHelper
def current_cart
if !session[:cart_id].nil?
Cart.find(session[:cart_id])
else
Cart.new
end
end
end
Cart_items_controller
class CartItemsController < ApplicationController
def create
#cart = current_cart
#cart_item = #cart.cart_items.new(cart_params)
#cart.save
redirect_back fallback_location: root_path
flash[:success] = "Items added to your cart"
session[:cart_id] = #cart.id
end
def destroy
#cart = current_cart
#cart_item = #cart.cart_items.find(params[:id])
#cart_item.destroy
#cart_items = current_cart.cart_items
redirect_to carts_path
end
private
def cart_params
params.require(:cart_item).permit(:product_id, :user_id)
end
end
Cart Controller
class CartsController < ApplicationController
def index
end
def show
#cart_items = current_cart.cart_items
#order_item = current_order.order_items.new
end
def new
end
end
My module association
class CartItem < ApplicationRecord
belongs_to :cart
belongs_to :product
end
class Cart < ApplicationRecord
has_many :cart_items
end
class User < ApplicationRecord
has_one :cart
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
end
class Product < ApplicationRecord
has_one_attached :image
has_many :order_items
has_many :cart_items
end
Product controller show
def show
#product = Product.find(params[:id])
#cart_item = current_cart.cart_items.new
end
Product view show
...
<%= form_for #cart_item, remote: true do |f|%>
<%= f.hidden_field :product_id, :value => #product.id %>
<%= f.hidden_field :user_id, :value => current_user.id %>
<%= f.submit "Add to Card ", class: "btn btn-primary"%>
<% end %>
So my Schema
create_table "cart_items", charset: "utf8mb3", force: :cascade do |t|
t.integer "cart_id"
t.integer "product_id"
t.integer "user_id"
end
create_table "carts", charset: "utf8mb3", force: :cascade do |t|
t.integer "user_id"
end
create_table "products", charset: "utf8mb3", force: :cascade do |t|
t.string "title"
t.string "description"
t.integer "price"
end
create_table "users", charset: "utf8mb3", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "name"
end
So what I get after adding to cart.
enter image description here
I think problem with finding session[:cart_id] It return nil and after every sign in it create a new cart
Can anyone help me?
Your current_cart helper is creating a new cart any time there isn't one in the session. In a new session, there of course won't be a cart, even if the user has had one previously, so if you want to use their latest cart instead of always creating a new one, you could do something like this:
def current_cart
if session[:cart_id].present?
Cart.find(session[:cart_id])
else
current_user.cart || current_user.build_cart
end
end
You should probably also make sure that a cart can't be created without an associated user, or you might end up with orphaned carts / cart items in your database, like in the screenshot you posted. The cart should have a belongs_to :user association, and user_id could be a foreign key with null: false on the database layer if you wanted to be extra safe. In your User model, the relation to cart could be has_one :cart, dependent: :destroy to make sure that the user's cart is destroyed when the user gets destroyed.
Also, it seems redundant to have a user_id in your cart_items table, since the user will of course be the same as in the associated cart. A has_one :user, through: :cart association should take care of that.

Rails Associations - Creating a new record through a form with belongs_to

I have a Course model:
class Course < ApplicationRecord
has_many :sub_courses
validates :title, presence: true
# Course associated to SubCourse via 'sub_course_id' on Course table
end
And a SubCourse model:
class SubCourse < ApplicationRecord
belongs_to :course
# SubCourse associated to Course via 'course_id' on SubCourse table
end
On the courses.show.html (specific course page e.g. admin/courses/1) I have a button that links to a new sub course page
%table
%tr
%td= #course.title
%td= #course.description
= button_to "Add New Sub Course", new_admin_sub_course_path(course_id: #course.id), method: :post
The new sub_course page sub_courses.new.html form.
= form_for #sub_course, url: admin_sub_courses_path do |f|
= f.label :title
= f.text_field :title
= f.label :description
= f.text_field :description
= f.submit
When going to the sub course new page I see the error No route matches [POST] "/admin/sub_courses/new"
My sub_course_controller.rb looks like this:
def new
#course = Course.find(params.require(:course_id))
#sub_course = #course.sub_course.new
end
def create
if #sub_course.save
redirect_to admin_sub_courses_path, notice: "saved"
else
render "new"
end
end
And my routes looks like this:
namespace :admin do
resources :courses, { :only => [:index, :new, :create, :edit, :destroy, :update, :show] }
resources :sub_courses
end
How do I successfully create a sub_course thats automatically associated with its course from the original show page I came from?
Schema structure looks like this:
create_table "courses", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "title"
t.string "description"
t.integer "sub_course_id"
end
create_table "sub_courses", force: :cascade do |t|
t.string "title"
t.text "description"
t.string "question"
t.string "possible_answer"
t.string "correct_answer"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "course_id"
end
After running rake routes for sub courses:
admin_sub_courses GET /admin/sub_courses(.:format) admin/sub_courses#index
POST /admin/sub_courses(.:format) admin/sub_courses#create
new_admin_sub_course GET /admin/sub_courses/new(.:format) admin/sub_courses#new
edit_admin_sub_course GET /admin/sub_courses/:id/edit(.:format) admin/sub_courses#edit
admin_sub_course GET /admin/sub_courses/:id(.:format) admin/sub_courses#show
PATCH /admin/sub_courses/:id(.:format) admin/sub_courses#update
PUT /admin/sub_courses/:id(.:format) admin/sub_courses#update
DELETE /admin/sub_courses/:id(.:format) admin/sub_courses#destroy
First of all I would check the result of rake routes |grep sub_courses.
At a first glance, though, it seems to me there is a problem of value assignment in the sub_course controller.
I would try to refactor it as follows:
before_action :set_course, only: [:new, :create]
def new
#sub_course = #course.sub_courses.new
end
def create
#sub_course = #course.sub_courses.new(sub_course_params)
if #sub_course.create!(sub_course_params)
redirect_to admin_sub_courses_path, notice: "saved"
else
render "new"
end
end
private
def sub_course_params
params.require(:sub_course).permit(:title, :description, :question, :possible_answer, :correct_answer, :course_id)
end
def set_course
#course = Course.find(params[:course_id])
end
As you see, I removed the line with Course.find(params.require(:course_id)) with the the correct syntax for finding a record through a params.
The params.require syntax is instead used by strong parameters, which need to be defined, in order to actually persist data from forms into records in the database.
See https://api.rubyonrails.org/classes/ActionController/StrongParameters.html
I then moved the #course assignment into a private method set_course This way the assignment is done just once and is shared by the methods that need it.

How do I set/limit the input in a rails form to match another models table?

I'm quite new to programming and haven't been able to find any resources to help me with this. I have created two scaffolds Accounts and Cashbooks. I want users to be able to add an account (with a parameter of account_name) which will then be set as the parameters for the Cashbook account input. Ideally when adding a Cashbook transaction, I would want users to see a dropdown which will contain all the account_names of the Accounts which have been created.
Cashbook Model
class Cashbook < ActiveRecord::Base
belongs_to :account, foreign_key: :account_name
end
Cashbook Controller
def new
#cashbook = Cashbook.new
#cashbook.account(account_params[:account_name])
end
def create
#cashbook = Cashbook.new(cashbook_params)
#cashbook.account(account_params[:account_name])
def cashbook_params
params.require(:cashbook).permit(:date, :description, :account, :kind, :amount, :balance, :name)
params.require(:account).permit(:account_name)
end
Cashbook DB
class CreateAccounts < ActiveRecord::Migration
def change
create_table :accounts do |t|
t.string :account_name
t.boolean :operating_expense
t.boolean :cost_of_sales
t.boolean :sales
t.boolean :other_income
t.boolean :non_current_liability
t.boolean :non_current_asset
t.boolean :current_asset
t.boolean :current_liability
t.boolean :equity
t.integer :account_number
t.timestamps null: false
end
end
end
Account Model
class Account < ActiveRecord::Base
has_many :cashbook, foreign_key: :account_name
end
Account Controller
def new
#account = Account.new
end
def edit
end
def create
#account = Account.new(account_params)
end
def account_params
params.require(:account).permit(:account_name, :operating_expense, :cost_of_sales, :sales, :other_income, :non_current_liability, :non_current_asset, :current_asset, :current_liability, :equity, :account_number)
end
Cashbook db
class CreateCashbooks < ActiveRecord::Migration
def change
create_table :cashbooks do |t|
t.date :date
t.string :description
t.boolean :account
t.string :kind
t.integer :amount
t.timestamps null: false
end
end
end
You can use the collection_select form helper.
In your case, it could look something like
<%= collection_select(:cashbook, :account_name, Account.all, :account_name, :account_name) %>
Though you should also consider using the id column to associate models as per the Rails Associations Guide. If you change your models to use ids as the foreign key, you won't need to explicitly specify the foreign key in your has_many and belongs_to statements, and you can still create a select box that displays account names:
<%= collection_select(:cashbook, :account_id, Account.all, :id, :account_name) %>

Assigns to events via checkboxes

I need assign to events. I have user edit with checkboxes, where i choose event and after click on save i'd like add insert to database with event_id and user_id. I don't know if is it good idea, but if somebody have better ideas please give me a piece of advice.
My view, On this moment i only know to which event i'm assign:
<% Event.all.each do |event| -%>
<tr>
<td><%= label_tag :event_ids, event.name -%></td>
<td><%= check_box_tag :event_ids, event.id, #user.event.include?(event), :name => 'user[event_ids][]' -%></td>
</tr>
<% end -%>
My schema:
create_table "users", force: true do |t|
t.string "name"
t.string "email"
end
create_table "events", force: true do |t|
t.string "name"
t.datetime "event_date"
t.string "organizator"
t.string "email"
end
create_table "bookings", force: true do |t|
t.integer "user_id"
t.integer "event_id"
end
My models:
class User < ActiveRecord::Base
has_many :bookings
has_many :events, :through => :bookings
class Booking < ActiveRecord::Base
belongs_to :user
belongs_to :event
class Event < ActiveRecord::Base
has_many :bookings
has_many :users, :through => :bookings
Where you have User > Booking > Event, I have Gallery > Entry > Photo.
In the controller for edit:
#photo_ids = []
#gallery.entries.each do |entry|
#photo_ids << entry.photo.id
end
The pertinent part of _form.html.haml is:
- #photos.in_groups_of(15, false) do |group|
%tr
- group.each do |photo|
%td
= label_tag("photo_ids_#{photo.id}", image_tag(photo.image.url(:thumb)))
= raw('<br/>')
= check_box_tag "photo_ids[]", photo.id, #photo_ids.include?(photo.id), :id => "photo_ids_${photo.id}"
And when saving in the controller:
if #gallery.save
photo_ids = params[:photo_ids]
photo_ids ||= []
#gallery.entries.each |entry|
entry_photo_id_s = entry.photo_id.to_s
# if already in gallery and checked, keep it, and remove from list to add
if photo_ids.include?(entry_photo_id_s)
photo_ids.delete(entry_photo_id_s)
else
entry.destroy
end
# if not already in gallery, add an entry
photo_ids.each do |photo_id|
entry = Entry.new
entry.gallery_id = #gallery.id
entry.photo_id = photo_id
entry.save
end
end
blah, blah, blah (render, redirect, whatever)
end
With a little translation of entities (and from HAML to ERB), that should work for your User > Booking > Event.

Rails controller use other db for destroy method (update, create not working too, only show)

i have such model:
class ToType < ActiveRecord::Base
attr_accessible :Name, :TYP_CCM, :TYP_CCM_TAX, :TYP_CDS_ID, :TYP_CTM, :TYP_CYLINDERS, :TYP_DOORS, :TYP_HP_FROM, :TYP_HP_UPTO, :TYP_ID, :TYP_KV_ABS_DES_ID, :TYP_KV_ASR_DES_ID, :TYP_KV_AXLE_DES_ID, :TYP_KV_BODY_DES_ID, :TYP_KV_BRAKE_SYST_DES_ID, :TYP_KV_BRAKE_TYPE_DES_ID, :TYP_KV_CATALYST_DES_ID, :TYP_KV_DRIVE_DES_ID, :TYP_KV_ENGINE_DES_ID, :TYP_KV_FUEL_DES_ID, :TYP_KV_FUEL_SUPPLY_DES_ID, :TYP_KV_MODEL_DES_ID, :TYP_KV_STEERING_DES_ID, :TYP_KV_STEERING_SIDE_DES_ID, :TYP_KV_TRANS_DES_ID, :TYP_KV_VOLTAGE_DES_ID, :TYP_KW_FROM, :TYP_KW_UPTO, :TYP_LA_CTM, :TYP_LITRES, :TYP_MAX_WEIGHT, :TYP_MMT_CDS_ID, :TYP_MOD_ID, :TYP_PCON_END, :TYP_PCON_START, :TYP_RT_EXISTS, :TYP_SORT, :TYP_TANK, :TYP_VALVES, :is_in_to
set_primary_key :TYP_ID
belongs_to :to_model
has_many :to_articles, :dependent => :destroy
end
class ToArticle < ActiveRecord::Base
attr_accessible :details, :manufacturer, :name, :oem_number, :only_with_vin, :quantity, :type_id
belongs_to :to_type
end
(some db is converted from big catalog, so rails conventions are a little bit missed)
my show view of to_type is:
part of it:
%td
= link_to "Подробнее", admin_catalog_to_to_article_path(c), :class=>'btn btn-primary'
= link_to "Редактирование", edit_admin_catalog_to_to_type_path(c), :class=>'btn btn-warning'
= link_to "Удалить", admin_catalog_to_to_type_path(c), :confirm => "!!!Тип #{c.Name} будет удалён!!!! Вы уверены?", :method => :delete, :class => "btn btn-danger"
my show action work normally, also controller:
class Admin::Catalog::To::ToTypesController < ApplicationController
respond_to :html
before_filter :auth_user
def auth_user
redirect_to new_admin_session_path unless admin_signed_in?
end
def show
#mod_id = params[:id]
#man = ToType.find(:all, conditions: {:TYP_MOD_ID => #mod_id}, order: "Name ASC")
render :layout => 'admin'
end
def edit
#man = ToType.find(params[:id])
render :layout => 'admin'
end
def update
#man = ToType.find(params[:id])
if #man.update_attributes(params[:to_type])
redirect_to admin_catalog_to_to_type_path(#man.TYP_MOD_ID)
else
render :layout => 'admin'
end
end
def new
#man = ToType.new
#mod_id = params[:mod_id]
render :layout => 'admin'
end
def create
#man = ToType.new(params[:to_type])
#mod_id = params[:mod_id]
#man.TYP_MOD_ID = #mod_id
if #man.save
redirect_to admin_catalog_to_to_type_path(#mod_id)
else
render :layout => 'admin'
end
end
def destroy
#man = ToType.find(params[:id])
if #man.destroy
redirect_to admin_catalog_to_to_type_path(#man.TYP_MOD_ID)
else
render :layout => 'admin'
end
end
end
and route:
namespace :admin do
namespace :catalog do
namespace :to do
resources :to_manufacturers,
:to_models,
:to_types,
:to_articles
end
end
end
but when i try to call destroy method i get:
ActiveRecord::StatementInvalid in Admin::Catalog::To::ToTypesController#destroy
Mysql2::Error: Unknown column 'to_articles.to_type_id' in 'where clause': SELECT `to_articles`.* FROM `to_articles` WHERE `to_articles`.`to_type_id` = 26923
also when i try edit or create i get:
undefined method `model_name' for NilClass:Class
i think that something is bad with connection with model: with update and create it didn't initialize object.
With destroy it use other! db. What happens?
Also i try to recreate it all and rename, nothing... Could understand what wrong... Also when in model i write which db table to use same errors appear.
when i try to add new object via console all is ok.
upd:
class CreateToTypes < ActiveRecord::Migration
def change
create_table :to_types, :primary_key => :TYP_ID do |t|
t.integer :TYP_ID
t.integer :TYP_CDS_ID
t.integer :TYP_MMT_CDS_ID
t.integer :TYP_MOD_ID
t.binary :TYP_CTM
t.binary :TYP_LA_CTM
t.integer :TYP_SORT
t.integer :TYP_PCON_START
t.integer :TYP_PCON_END
t.integer :TYP_KW_FROM
t.integer :TYP_KW_UPTO
t.integer :TYP_HP_FROM
t.integer :TYP_HP_UPTO
t.integer :TYP_CCM
t.integer :TYP_CYLINDERS
t.integer :TYP_DOORS
t.integer :TYP_TANK
t.integer :TYP_KV_VOLTAGE_DES_ID
t.integer :TYP_KV_ABS_DES_ID
t.integer :TYP_KV_ASR_DES_ID
t.integer :TYP_KV_ENGINE_DES_ID
t.integer :TYP_KV_BRAKE_TYPE_DES_ID
t.integer :TYP_KV_BRAKE_SYST_DES_ID
t.integer :TYP_KV_FUEL_DES_ID
t.integer :TYP_KV_CATALYST_DES_ID
t.integer :TYP_KV_BODY_DES_ID
t.integer :TYP_KV_STEERING_DES_ID
t.integer :TYP_KV_STEERING_SIDE_DES_ID
t.float :TYP_MAX_WEIGHT
t.integer :TYP_KV_MODEL_DES_ID
t.integer :TYP_KV_AXLE_DES_ID
t.integer :TYP_CCM_TAX
t.float :TYP_LITRES
t.integer :TYP_KV_DRIVE_DES_ID
t.integer :TYP_KV_TRANS_DES_ID
t.integer :TYP_KV_FUEL_SUPPLY_DES_ID
t.integer :TYP_VALVES
t.integer :TYP_RT_EXISTS
t.string :Name
t.boolean :is_in_to
t.string :fuel_type
end
end
end
class CreateToArticles < ActiveRecord::Migration
def change
create_table :to_articles do |t|
t.string :oem_number
t.string :manufacturer
t.text :name
t.integer :quantity
t.text :details
t.boolean :only_with_vin
end
end
end
you don't have relationship between ToArticle and ToType in database.
use belongs_to in ToArticle migration
check rails guide on associations

Resources