Meal ordering system and associations - ruby-on-rails

I'm doing meal ordering system in Rails, I made model for User, Restaurant, Meal and Order and I don't know which associations should I use to connect all these models together. For now my models looks like this:
class User < ApplicationRecord
has_many :orders
has_many :restaurants, through: :orders
end
class Order < ApplicationRecord
belongs_to :user
belongs_to :restaurant
end
class Meal < ApplicationRecord
belongs_to :restaurant
end
class Restaurant < ApplicationRecord
has_many :orders
has_many :meals
has_many :users, through: :orders
end
Now when I'm using form to order some meal and save this order in the database I'm getting error in log:
Parameters: {"utf8"=>"✓", "authenticity_token"=>"+pwoJ/82k/2SiS7z4X4nVHyaKCMMfWCECQe6TufnkpNaW9PEgvlwxlf3skAH2QQupSLIoe81Z/I0CleL/m9cjw==", "orders"=>{"restaurant_id"=>"2", "meal_id"=>"2", "suggestions"=>""}, "commit"=>"Place your order"}
Unpermitted parameters: restaurant_id, meal_id
(0.1ms) begin transaction
(0.1ms) rollback transaction
Redirected to http://localhost:3000/
Completed 302 Found in 8ms (ActiveRecord: 0.2ms)
My order controller looks like this:
class OrdersController < ApplicationController
def new
#order = Order.new
end
def create
#order = Order.new(order_params)
if #order.save
redirect_to root_path
flash[:success] = "Your order has been added"
else
flash[:danger] = "Error"
redirect_to root_path
end
end
private
def order_params
params.require(:orders).permit(:restaurant, :meal, :suggestions)
end
end
When I change def order_params to:
params.require(:orders).permit(:restaurant_id, :meal_id, :suggestions)
I'm getting
unknown attribute 'restaurant_id' for Order.
I assume that it's bad associations fault, can anyone help me?
* UPDATE *
Now I'm getting error in my log when I'm trying to save order:
Started POST "/new_order" for 127.0.0.1 at 2016-12-19 11:18:50 +0100
Processing by OrdersController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"nnDqY0FVzUslTJ1VoL57vnlO6aSTLcVuenT1GJwloJ8+txGAPJoucOAyAeZGGVjEoPYJJnBlwhhHeRjdha1ugw==", "orders"=>{"restaurant_id"=>"4", "meal_id"=>"26", "suggestions"=>""}, "commit"=>"Place your order"}
(0.1ms) begin transaction
Restaurant Load (0.1ms) SELECT "restaurants".* FROM "restaurants" WHERE "restaurants"."id" = ? LIMIT ? [["id", 4], ["LIMIT", 1]]
(0.1ms) rollback transaction
Redirected to http://localhost:3000/
Completed 302 Found in 6ms (ActiveRecord: 0.3ms)
* Update 2 *
When I change for #order.save! I get:
Validation failed: User must exist
With order.errors.inspect I just get:
(0.2ms) rollback transaction
No template found for OrdersController#create, rendering head :no_content
Completed 204 No Content in 97ms (ActiveRecord: 1.8ms)
I'm using omniauth to sign in before user can order a meal and this is only way to sign up or sign in. When I created Order model I used user:references, you think this can be a reason?

You said that you have a sign_in/sign_up, so I assume that you have a current_user in your controller.
In order to save your order you need to supply user_id, here is a one way to do it
def order_params
params.require(:orders).permit(:restaurant, :meal, :suggestions)
.merge(user_id: current_user.id)
end

Related

How do I display the value of a session variable in Ruby/Rails?

First I don't know if I'm declaring a session variable correctly. Also, I put it in the application controller, because I know it will run. If there is a better place to put this, then please let me know. The desired outcome is a user selects the team they want all their work to save to during the session. Then when objects are saved they use the session variable :current_team instead of :current_user. By default on creation a team will be created for each new user. That is why I'm querying for the first team for each user. During the session I plan for the user to select the team they are working/contributing for and the selection persist until they change it.
application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_current_team
def set_current_team
return unless session[:current_team]
session[:current_team] = Team.first.where(user_id: current_user).id
end
end
index.html.erb
<p><%= session[:current_team] %></p>
output
Processing by PagesController#index as HTML
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 ORDER BY "users"."id" ASC LIMIT $2 [["id", 1], ["LIMIT", 1]]
↳ /home/dev/.rbenv/versions/2.5.2/lib/ruby/gems/2.5.0/gems/activerecord-5.2.3/lib/active_record/log_subscriber.rb:98
Rendering pages/index.html.erb within layouts/application
Rendered pages/index.html.erb within layouts/application (0.5ms)
Rendered shared/_navigation.html.erb (2.9ms)
Completed 200 OK in 172ms (Views: 168.3ms | ActiveRecord: 0.4ms)
My models are Team, User, and a join table Team_member.
team_member.rb
belongs_to :user
belongs_to :team
belongs_to :role
end
team.rb
has_many :team_members
has_many :users, through: :team_members
has_many :roles, through: :team_members
end
I assume one user can be part of multiple teams and (obviously) one team has many users, your relation would be roughly like this
class User < ApplicationRecord
has_and_belongs_to_many :teams
end
class Team < ApplicationRecord
has_and_belongs_to_many :users
end
You are already in right direction but I made little changed to set_current_team and added accessor method to get current team instance.
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_current_team
helper_method :current_team
def set_current_team
return unless session[:current_team]
session[:current_team] = current_user.teams.first.id
end
def current_team
#_team ||= Team.find(session[:current_team])
end
end
The current_team method uses memoization technique to cache the current team during the request so that if you call current_team multiple times during a request, it will not call the DB again to get the team record.
I strongly recommend to read this article 4 Simple Memoization Patterns in Ruby.
Also notice helper_method macro above the set_current_team which makes this helper method available to your view as well.
so following will work on your index
<p><%= current_team %></p>
as well as across all controllers as all controllers inherits from ApplicationController
Once you have session[:current_team], you can save it to a variable in any controller. Say for instance in the teams controller, you can do: #current_team = session[:current_team]. Then in the view you can use #current_team.
update set_current_team method to
class ApplicationController < ActionController::Base
before_action :authenticate_user!
before_action :set_current_team
# only set the session[:current_team] if it is nil (when user login)
def set_current_team
session[:current_team] ||= current_user.teams.first.id
end
# to access current_team
def current_team
#current_team ||= Team.find(session[:current_team])
end
end
set session[:current_team] to user selected team id in controller#action that handles the request
set session[:current_team] to nil when user logout

Save on model has_many to has_many doesn't trigger on build

We have this model called "Cliente" (cliente.rb):
class Cliente < ApplicationRecord
has_many :clientes_hardwares
has_many :alertas_clientes
has_many :sucursales
has_many :alertas, through: :alertas_clientes
has_many :hardwares, through: :clientes_hardwares
end
The SQL table:
And the model "Alerta" (alerta.rb):
class Alerta < ApplicationRecord
has_many :alertas_clientes
has_many :clientes, through: :alertas_clientes
end
The SQL Table:
And after that we created a join table.
class CreateJoinTableClientesAlertas < ActiveRecord::Migration[5.2]
def change
create_join_table :clientes, :alertas do |t|
# t.index [:cliente_id, :alerta_id]
# t.index [:alerta_id, :cliente_id]
end
end
end
The SQL table is called "alertas_clientes" and his structure very simple
The model is file (alertas_cliente.rb):
class AlertasCliente < ApplicationRecord
belongs_to :cliente
belongs_to :alerta
end
We want to save the relation on the table but the console doesn't show the actual error.
def savenoti
begin
#cliente = Cliente.find(6)
#cliente.alertas_clientes.build(
:alerta => Alerta.find(1)
)
#cliente.save
rescue => exception
puts exception.message
flash[:alert] = 'Error al enviar alerta.'
redirect_to action: 'index'
end
end
But the console shows:
Processing by AlertasController#sendnoti as HTML
Cliente Load (0.3ms) SELECT `clientes`.* FROM `clientes` WHERE `clientes`.`id` = 6 LIMIT 1
↳ app/controllers/alertas_controller.rb:37
Alerta Load (0.2ms) SELECT `alerta`.* FROM `alerta` WHERE `alerta`.`id` = 1 LIMIT 1
↳ app/controllers/alertas_controller.rb:39
(0.1ms) BEGIN
↳ app/controllers/alertas_controller.rb:41
(0.1ms) ROLLBACK
↳ app/controllers/alertas_controller.rb:41
wrong number of arguments (given 1, expected 0)
Am I missing something?
I already know what really happend, the issue was a name field, all the relatiosn were good, but the problem was that the Model "Alerta" has a field called "send" that apparently is a reserved word of ruby. I changed to "sended" and all works fine.
I know is strange but it would be nice if Rails can show that type of error.
Looking at the queries displayed by the error it seems that you haven't created an Alerta when calling Alerta.find(1) inside savenoti.
Assuming that you haven't created the Alerta but you have already saved the Client, you could do:
def savenoti
begin
#cliente = Cliente.find(6)
alerta = Alerta.new # or a different builder if you have any info to save
#cliente.alertas.create(alerta) //this will create and save the `AlertasCliente` instance.
rescue => exception
puts exception.message
flash[:alert] = 'Error al enviar alerta.'
redirect_to action: 'index'
end
end
If that isn't the case, and you already saved the Alerta to the database, then include the code in lines 39 and 41 as requested by #Beartech.
In any case I would use the plain .alerts since it's clearer and that's the whole idea of declaring the through: :alertas_clientes on the model. There are more ways of creating a new record for a many_to_many association as explained here.

Rails belongs_to multiple models with accept_nested_attributes_for

I'm building an invoice system for a car trader where each invoice is linked to one customer and one vehicle, with customers potentially having many invoices and vehicles also having many invoices. I have got it working with one nested model doing the following:
purchase_invoice.rb
class PurchaseInvoice < ApplicationRecord
belongs_to :vehicle
accepts_nested_attributes_for :vehicle
end
vehicle.rb
class Vehicle < ApplicationRecord
has_many :purchase_invoices
end
purchase_invoices_controller.rb
def new
#vehicle = Vehicle.new
#purchase_invoice = #vehicle.purchase_invoices.build
end
def create
#vehicle = Vehicle.new
#purchase_invoice = #vehicle.purchase_invoices.build(invoice_params)
if #purchase_invoice.save
redirect_to #purchase_invoice
else
render 'new'
end
end
private
def invoice_params
params.require(:purchase_invoice).permit(:buyer, :location, :vehicle_price, :transfer_fee, :balance_due, :payment_cash, :payment_bank_transfer, :payment_comment, :status, vehicle_attributes: [:vrm, :date_first_registered, :make, :model, :colour, :transmission, :vin, :fuel, :power])
end
new.html.erb
<%= form_with model: #purchase_invoice, local: true do |form| %>
<%= form.fields_for #vehicle do |vehicle_form| %>
<% end %>
<% end %>
However when I add a second relationship like this:
purchase_invoice.rb
class PurchaseInvoice < ApplicationRecord
belongs_to :customer
belongs_to :vehicle
accepts_nested_attributes_for :customer
accepts_nested_attributes_for :vehicle
end
I get an error saying :'Unpermitted parameters: :vehicle'
Does anybody know why? Also, how would I modify the controller new/create action for build whilst maintaining strong params?
I've been Googling this for four hours now and tried a lot but had no luck. Thanks in advance to everybody!
Update
Here's my logs:
Started POST "/purchase_invoices" for 127.0.0.1 at 2018-03-20 15:10:01 +0000
Processing by PurchaseInvoicesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"JB8py9zNxew6aQ6/za3JHDEb4j8f9HGujTlS6P1Eyhb+5NtPPP47fW7AHBkt9eURcnXg0gh9Mf1DCKCSwvlAbg==", "purchase_invoice"=>{"customer"=>{"name"=>""}, "vehicle"=>{"vrm"=>"SA07SSX", "make"=>"VAUXHALL", "model"=>"MERIVA DESIGN", "colour"=>"Silver", "vin"=>"W0L0XCE7574216645", "date_first_registered"=>"20/03/2007"}, "vehicle_odomoter_reading"=>"", "vehicle_number_of_keys"=>"", "vehicle_mot_expiry"=>"", "vehicle_hpi_clear"=>"", "vehicle_comments"=>"", "buyer"=>"", "location"=>"", "vehicle_price"=>"", "transfer_fee"=>"0", "balance_due"=>"", "payment_cash"=>"", "payment_bank_transfer"=>"", "payment_comments"=>""}, "commit"=>"Create Purchase invoice"}
Vehicle Load (0.3ms) SELECT "vehicles".* FROM "vehicles" WHERE "vehicles"."vrm" = ? ORDER BY "vehicles"."id" ASC LIMIT ? [["vrm", "SA07SSX"], ["LIMIT", 1]]
Unpermitted parameters: :customer, :vehicle, :payment_comments
(0.1ms) begin transaction
(0.1ms) rollback transaction
Rendering purchase_invoices/new.html.erb within layouts/application
Rendered purchase_invoices/new.html.erb within layouts/application (10.2ms)
Rendered layouts/_header.html.erb (1.7ms)
Completed 200 OK in 63ms (Views: 54.4ms | ActiveRecord: 0.4ms)
You approach has some issues but you are very close. The PurchaseInvoice model belongs to a Vehicle and a Customer, and it stores a customer_id and a vehicle_id. That's correct. As you said in the comment, it is much more than just a join model, because it holds many other data, such as price, transfer fees, etc. Anyway, you are passing a lot of params regarding the vehicle to be purchased, and not the id of the vehicle. In fact, you are creating the vehicle in the PurchaseInvoice create, which makes no sense. Moreover, your vehicle_attributes should not be an array, but a hash (because PurchaseInvoice belongs_to :vehicle; so it is just one), and should only have the vehicle_id. And giving that you just need the vehicle_id, you do not need nested_attributes (neither for customer) . I would change:
The controller:
def new
#vehicles = Vehicle.all #All vehicles, to select from the list
#customers = Customer.all #All customers, to select from the list (unless you use current_user)
#purchase_invoice = PurchaseInvoice.new
end
def create
#vehicle = Vehicle.find(params[:vehicle_id])
#customer = Customer.find(params[:customer_id]) # Or use current_user
#The above is only needed to check if vehicle and customer exist, but it is not needed below
#purchase_invoice = PurchaseInvoice.create(invoice_params)
if #purchase_invoice.save
redirect_to #purchase_invoice
else
render 'new'
end
end
def invoice_params
params.require(:purchase_invoice).permit(:customer_id, vehicle_id, :location, :vehicle_price, :transfer_fee, :balance_due, :payment_cash, :payment_bank_transfer, :payment_comment, :status)
end
The view:
<%= form_with model: #purchase_invoice, local: true do |form| %>
<!-- This is necessary to choose the customer. If the customer is current_user, just remove it. -->
<%= form.collection_select(:customer_id, #customers, :id, :name %>
<!-- This is necessary to choose the vehicle. This is extremely simplified -->
<!-- The customer should be able to look at many attributes of the car -->
<!-- to be able to select one to purchase -->
<%= form.collection_select(:vehicle_id, #vehicles, :id, :make %>
<!-- All other invoice fields. -->
<% end %>

rails4 has_one association scope with where query

I am developing a Product model that has an has_one association to itself including a raw material record. And the model returns data through REST API. I am using Active Model Serializer instead of JBuilder.
Product model has a 'code' field that contain a product code in string:
'001-000-01-01' (This is a product.)
'001-000-00-01' (This is a material.)
Only difference between two codes is the third number from right. '1' is product. '0' is material. I want to include a "raw_material" record when retrieving a product record. So, I try to set has_one association with scope that has a "where" clause (later I can compose a query to get a material from a product code). Now I simply pass "product" object in lambda and use it in where.
First I write in def raw_material, this works. However, I don't know how to pass an object to def and use it in where clause. Therefore I come up with the scope pattern in has_one, however it returns an error even though it generates exactly the same SELECT as the def pattern. I get "NoMethodError" instead.
class Product < ActiveRecord::Base
has_many :supplies, ->{order('row_order ASC') }, primary_key: :code, foreign_key: :product_code
#This Works!
#has_one :raw_material, class_name: 'Product', primary_key: :code, foreign_key: :code
#This Works!
#has_one :raw_material
#Does not work. Why?
has_one :raw_material, ->(product) { where('code = ?', product.code).take }, class_name: 'Product'
accepts_nested_attributes_for :supplies, allow_destroy: true
#def raw_material
# Product.where('code = ?', '001-000-01-01').take
#end
end
The "def" pattern works:
Started GET "/products/1.json" for ::1 at 2016-10-05 21:48:15 +0900
Processing by ProductsController#show as JSON
Parameters: {"id"=>"1"}
Product Load (0.3ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
[active_model_serializers] Supply Load (0.1ms) SELECT "supplies".* FROM "supplies" WHERE "supplies"."product_code" = ? ORDER BY row_order ASC [["product_code", "031-052-00-01"]]
[active_model_serializers] Product Load (0.1ms) SELECT "products".* FROM "products" WHERE (code = '001-000-01-01') LIMIT 1
[active_model_serializers] Rendered ProductSerializer with ActiveModelSerializers::Adapter::Attributes (6.71ms)
Completed 200 OK in 24ms (Views: 9.2ms | ActiveRecord: 1.4ms)
However, the scope pattern does not work:
Started GET "/products/1.json" for ::1 at 2016-10-06 08:19:13 +0900
Processing by ProductsController#show as JSON
Parameters: {"id"=>"1"}
Product Load (0.1ms) SELECT "products".* FROM "products" WHERE "products"."id" = ? LIMIT 1 [["id", 1]]
[active_model_serializers] Supply Load (0.1ms) SELECT "supplies".* FROM "supplies" WHERE "supplies"."product_code" = ? ORDER BY row_order ASC [["product_code", "031-052-00-01"]]
[active_model_serializers] Product Load (0.1ms) SELECT "products".* FROM "products" WHERE (code = '031-052-00-01') LIMIT 1
[active_model_serializers] Rendered ProductSerializer with ActiveModelSerializers::Adapter::Attributes (10.6ms)
Completed 500 Internal Server Error in 27ms (ActiveRecord: 1.2ms)
NoMethodError (undefined method `except' for #<Product:0x007fe97b090418>):
app/controllers/products_controller.rb:17:in `block (2 levels) in show'
app/controllers/products_controller.rb:14:in `show'
Rendered vendor/bundle/ruby/2.2.0/gems/actionpack-4.2.5/lib/action_dispatch/middleware/templates/rescues/_source.erb (8.8ms)
Product Controller simply defines show like this:
# GET /products/1
# GET /products/1.json
def show
respond_to do |format|
format.html
format.json do
render json: #product, include: ['raw_material']
end
end
end
product_serializer.rb is:
class ProductSerializer < ActiveModel::Serializer
attributes(*Product.attribute_names.map(&:to_sym))
has_one :raw_material
end
Any assistance is greatly appreciated.
Thank you
UPDATE:
I solved this issue myself. Please check my answer below. Thank all who wrote solutions.
You can customise the has_one :raw_material as follows.
class ProductSerializer < ActiveModel::Serializer
attributes(*Product.attribute_names.map(&:to_sym))
has_one :raw_material do
Product.where(code: object.code)
end
end
I could solve this myself. Since I don't have to write this in a line, the def(method) pattern is the best bet. How to override "has_many" could help my issue.
Overriding a has_many association getter
"self" can be used for has_one to get an object inside def.
has_one :raw_material
def raw_material
Product.where('code like ?', (self.code)[0,9] + '_' + (self.code)[10,3]).take
end
This works and can generate any dataset you like in a Model.
# Does not work. Why?
has_one :raw_material ->(product) { where('code = ?', product.code).take }...
because you are trying to define a scope here... not an association. if you want a scope, use a scope eg:
scope :raw_material ->(product) { where('code = ?', product.code).first }
if you want an association, then use an association eg
has_one :raw_material, class_name: 'Product', primary_key: :code, foreign_key: :code
don't try to mix the two.

Controller not passing in values from the from to the create method

My Create controller passes in the for the child of a nested resource as null instead of passing the values i've just inputed into the form.
Here is my code
Routes:
resources :trips do
resources :pilgrims
end
Models:
Trip:
class Trip < ActiveRecord::Base
has_many :pilgrims
accepts_nested_attributes_for :pilgrims, :allow_destroy => true
attr_accessible :end_date, :leader_id, :name, :start_date, :pilgrim_attributes
end
Pilgrim:
class Pilgrim < ActiveRecord::Base
belongs_to :trip
attr_accessible :pilgrim_id, :surname, :name, :middle, :aka, :prefix, :address, :city, :state, :zip, :email, :telephone, :nationality, :date_of_birth, :passport_number, :expiration, :jordan, :room, :price, :status, :trip_id
end
My Pilgrim controller:
def new
#trip = Trip.find(params[:trip_id])
#pilgrim = Pilgrim.new
end
def create
#trip = Trip.find(params[:trip_id])
#pilgrim = #trip.pilgrims.build(params[:pilgrim])
if #pilgrim.save
flash[:notice] = "The <b>#{ #pilgrim.name }</b> has been created successfully."
redirect_to(trip_pilgrims_path, :notice => "The <b>#{ #pilgrim.name }</b> ship has been saved successfully.")
else
render(:new, :error => #pilgrim.errors)
end
end
Link to a gist with my form code Form
The routes seem to be correct, when i click on new_trip_pilgrim_path(#trip) it does point to trips/:trip_id/pilgrims/new and loads the new pilgrim form.
However when i click save on the form it redirects me to the route trips/3/pilgrims but shows the new pilgrim form saying all required fields were left blank.
This is what displays in the log.
Started POST "/trips/3/pilgrims" for 127.0.0.1 at 2013-01-19 22:12:06 -0800
Processing by PilgrimsController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"kOE06m3DNax43BOLYZ6t1lS7/T4wOWb2xM8m/mlQzvA=", "commit"=>"Create Pilgrim", "trip_id"=>"3"}
Trip Load (0.3ms) SELECT `trips`.* FROM `trips` WHERE `trips`.`id` = 3 LIMIT 1
(0.2ms) BEGIN
(0.2ms) ROLLBACK
Pilgrim Load (0.4ms) SELECT `pilgrims`.* FROM `pilgrims` WHERE `pilgrims`.`trip_id` = 3
Rendered pilgrims/_form.html.erb (36.4ms)
Rendered pilgrims/new.html.erb within layouts/application (37.2ms)
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 2 LIMIT 1
Trip Load (0.3ms) SELECT `trips`.* FROM `trips`
Completed 200 OK in 244ms (Views: 102.5ms | ActiveRecord: 11.2ms)
What is going on with the Pilgrim create controller?
You are trying to create a new pilgrim object by calling
#pilgrim = #trip.pilgrims.build(params[:pilgrim])
in the PilgrimsController. But Parameters: {"utf8"=>"✓", "authenticity_token"=>"kOE06m3DNax43BOLYZ6t1lS7/T4wOWb2xM8m/mlQzvA=", "commit"=>"Create Pilgrim", "trip_id"=>"3"} Doesn't contain any pilgrim paramters. So the problem is with the form used for creating a new pilgrim

Resources