Proper model set up - ruby-on-rails

I am new to stackoverflow, so my apologies if this is formatted poorly.
In my current project I have a model Driver, which has many trips. Those trips have many mileages, backhauls, picks, drops and hours. When I create a new trip, i want to be able to associate it to the driver, but I also want to be able to add the mileages, backhauls, picks and drops and hours on the same page. I am unsure how to structure my routes for this. I have been successful in creating a trip for a driver without adding on the additional models to the trip but from there I am stumped. I have only created the mileage model/controller so far for what needs to be associated with the trip. Any nudge in the right direction would be greatly appreciated.
Driver Model
class Driver < ApplicationRecord
has_many :trips
end
Trip Model
class Trip < ApplicationRecord
belongs_to :driver
has_many :mileages
accepts_nested_attributes_for :mileages
default_scope {order(date: :asc)}
validates :total, presence: true
validates :date, presence: true
validates_uniqueness_of :trip_number, :allow_nil => true, :allow_blank =>
true
end
Mileage Model
class Mileage < ApplicationRecord
belongs_to :trip
end
Trips controller
def index
#trips = Trip.all
end
def show
end
def new
#driver = Driver.find(params[:driver_id])
#trip = Trip.new
end
def edit
end
def create
#driver = Driver.find(params[:driver_id])
#trip = Trip.new(trip_params)
#driver.trips.create(trip_params)
respond_to do |format|
if #driver.trips.create(trip_params)
flash[:notice] = "Trip successfully created"
redirect_to new_driver_trip_path(#driver)
else
flash[:warning] = "Unable to create trip"
redirect_to new_driver_trip_path(#driver)
end
end
private
def set_trip
#trip = Trip.find(params[:id])
end
def trip_params
params.require(:trip).permit(:trip_number, :date, :driver_id, :total)
end
end
Mileage Controller
def new
#mileage = Mileage.new
end
def create
#mileage.create(mileage_params)
end
private
def mileage_params
params.require(:mileage).permit(:miles, :rate, :total)
end
end
end
Driver Controller
def index
#drivers = Driver.all
end
def show
end
def new
#driver = Driver.new
end
def edit
end
def create
#driver = Driver.new(driver_params)
respond_to do |format|
if #driver.save
format.html { redirect_to #driver, notice: 'Driver was
successfully created.' }
format.json { render :show, status: :created, location: #driver }
else
format.html { render :new }
format.json { render json: #driver.errors, status:
:unprocessable_entity }
end
end
end
private
def set_driver
#driver = Driver.find(params[:id])
end
def driver_params
params.require(:driver).permit(:first_name, :last_name, :email, :unit)
end
end

If you want to create nested models on the same page. i.e. milages within trip page using accepts_nested_attributes_for, You can use cocoon gem.
https://github.com/nathanvda/cocoon
Drifting Ruby has a video that shows the process in detail that is easy to follow.
https://www.youtube.com/watch?v=56xjUOAAZY8
You can do it manually as well but it will require a little bit more work.
With cocoon you will do have a Driver Controller and Trip controller but you don't need a Milage controller since it is handled with nested_attributes via Trip Controller.
If you want to do it manually, you will need a bit of JavaScript. You can follow Ryan Bates RailsCast on this topic.
railscasts.com/episodes/196-nested-model-form-revised

Related

handling nested attributes in rails 5 api only application

I have a requirement in rails api application. client can have many orders and each order belongs to a client.
class Client < ApplicationRecord
has_many :orders
end
my order.rb is
class Order < ApplicationRecord
belongs_to :client, dependent: :destroy
accepts_nested_attributes_for :client
validates_presence_of :order_amount, :service_amount, :miners_amount
end
I have a route exposed /place_order and which creates client and orders.
class OrderProcessingController < ApplicationController
def place_order
#order = Order.new(order_processing_params)
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
end
Everything works fine so far. Now my requirement is, i have to check the client is already present in client table. if yes add the client_id for the orders and create new order. I don't want to create new client and order every time.
how can i achieve the same in before_filter or something like that. get the client from client params and if the client present delete the params key from incoming params ???
the post data for place_order is as follows
{
"order" : {
"order_amount" : "10000",
"service_amount" : "1000",
"miners_amount" : "10000",
"client_attributes": {
"name": "Ajith",
"email": "ajith#gmail.com",
"phone_number": "12231321312",
"date": "12/12/12"
}
}
}
Thanks in advance,
Ajith
The below code is not tested, mostly your approach should be around this
class OrderProcessingController < ApplicationController
before_action :find_client, only: [:place_order]
def place_order
#order = #client.orders.new(order_processing_params)
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
def find_client
#client = Client.find_or_create_by(email: params[:order][:client_attributes][:email])
#below line can be improved using a method, see the last line if later you want, never update a primary key which is email in bulk params
#client.update_attributes(name: params[:order][:client_attributes][:name], phone_number: params[:order][:client_attributes][:phone_number], date: params[:order][:client_attributes][:date])
end
#def get_client_params
# params.require(:order)
#end
end
I tried below approach to get a solution. not very sure that this is the right way to approach the problem
class OrderProcessingController < ApplicationController
before_action :find_client, only: :place_order
def place_order
if #client.present?
#order = #client.orders.build(order_processing_params)
else
#order = Order.new(order_processing_params)
end
if #order.save
render json: #order
else
render json: #order.errors.full_messages
end
end
private
def order_processing_params
params.require(:order).permit(:order_amount, :service_amount, :miners_amount, client_attributes: [:name, :email, :phone_number, :date])
end
def find_client
begin
#client = Client.find_by_email(params[:order][:client_attributes][:email])
rescue
nil
end
end
end
Thanks,
Ajith

Ruby on Rails 'has_many :through', storing data

In my application I have a "bookings" table, and an "extras" table.
This is a many-many relationship. Therefore I have created a middle table called "additions"
I've used the "has_many :through" to establish the relationship between the tables:
class Booking < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Extra < ActiveRecord::Base
has_many :additions
has_many :extras, :through => :additions
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
This seems to work. I added a few extras to some existing bookings manually (by adding numbers to the additions table), and wrote code so that when you click to show a booking, it lists all associated extras.
Now I need to make it so that when you make a booking - the "extras" are saved into the middle (additions) table.
I have checkboxes on my bookings form page:
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
But obviously, the choices just get discarded when the user clicks on save.
I need some code to go (in the controller?) to make it save these "extras" into the "additions table" ?
Any ideas, as I can't work out how to do this?!
Thanks!
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
# before_action :find_extra
def index
#bookings = Booking.where("room_id = ? AND end_time >= ?", #room.id, Time.now).order(:start_time)
respond_with #bookings
end
def new
#booking = Booking.new(room_id: #room.id)
end
def create
#booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length, :user_id))
#booking.room = #room
if #booking.save
redirect_to room_bookings_path(#room, method: :get)
else
render 'new'
end
end
def show
#booking = Booking.find(params[:id])
end
def destroy
#booking = Booking.find(params[:id]).destroy
if #booking.destroy
flash[:notice] = "Booking: #{#booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{#booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(#room)
else
render 'index'
end
end
def edit
#booking = Booking.find(params[:id])
end
def update
#booking = Booking.find(params[:id])
# #booking.room = #room
if #booking.update(params[:booking].permit(:room_id, :start_time, :length, :user_id))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(#room)
end
else
render 'edit'
end
end
private
def save booking
if #booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(#room, #booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
#room = Room.find_by_id(params[:room_id])
end
end
# def find_extra
# if params[:extra_id]
# #extra = Extra.find_by_id(params[:extra_id])
# end
# end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Booking not found."
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
------------------------
class AdditionsController < ApplicationController
before_action :set_addition, only: [:show, :edit, :update, :destroy]
# GET /additions
def index
#additions = Addition.all
end
# GET /additions/1
def show
end
# GET /additions/new
def new
#addition = Addition.new
end
# GET /additions/1/edit
def edit
end
# POST /additions
def create
#addition = Addition.new(addition_params)
if #addition.save
redirect_to #addition, notice: 'Addition was successfully created.'
else
render :new
end
end
# PATCH/PUT /additions/1
def update
if #addition.update(addition_params)
redirect_to #addition, notice: 'Addition was successfully updated.'
else
render :edit
end
end
# DELETE /additions/1
def destroy
#addition.destroy
redirect_to additions_url, notice: 'Addition was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_addition
#addition = Addition.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def addition_params
params.require(:addition).permit(:booking_id, :extra_id, :extra_name)
end
end
--------------------------------------
# #author Stacey Rees <https://github.com/staceysmells>
class ExtrasController < ApplicationController
# #see def resource_not_found
around_filter :resource_not_found
before_action :set_extra, only: [:show, :edit, :update, :destroy]
def index
#extras = Extra.all
end
def show
end
def new
#extra = Extra.new
end
def edit
end
def create
#extra = Extra.new(extra_params)
if #extra.save
redirect_to #extra, notice: 'Extra was successfully created.'
else
render :new
end
end
def update
if #extra.update(extra_params)
redirect_to #extra, notice: 'Extra was successfully updated.'
else
render :edit
end
end
def destroy
#extra.destroy
redirect_to extras_url, notice: 'Extra was successfully destroyed.'
end
private
# Use callbacks to share common setup or constraints between actions.
def set_extra
#extra = Extra.find(params[:id])
end
# If resource not found redirect to root and flash error.
def resource_not_found
yield
rescue ActiveRecord::RecordNotFound
redirect_to root_url, :notice => "Room Category not found."
end
# Only allow a trusted parameter "white list" through.
def extra_params
params.require(:extra).permit(:extraimg, :name, :description, :quantity, :price, :extracat_id)
end
end
What you're doing here is working with nested form attributes. It's a bit complex, but it's also something people do often, so there are some good resources available.
I suggest you look at this post: http://www.sitepoint.com/complex-rails-forms-with-nested-attributes/
In particular, the section named 'More Complicated Relationships' specifically has an example of using nested attributes to set up a many-to-many association using has_many :through.
The key pieces (which commenters have already pointed out) are going to be accepts_nested_attributes_for :extras in your Booking model, and a f.fields_for :extras block in the view. You'll also need to modify your booking_params method to permit the nested values. There are a couple of strong parameters gotchas that you can potentially run into with that, so you may need to review the documentation.
It turns out I was nearly there with the code I had once the accepts_nested_attributes_for was written in.
My main issue was setting up the booking_params method in the controller. I got it to work by declaring :extra_ids => [] in my params.permit.

Rails 4 ActiveRecord, if record exists, use id, else create a new record

When an employee is created, he is given a title. If the title is unique, the record saves normally. If the title is not unique, I want to find the existing title, and use that instead. I can't figure out how to do this in the create action.
employer.rb
class Employee < ActiveRecord::Base
belongs_to :title, :class_name => :EmployeeTitle, :foreign_key => "employee_title_id"
accepts_nested_attributes_for :title
end
employer_title.rb
class EmployerTitle < ActiveRecord::Base
has_many :employees
validates :name, presence: true, length: { maximum: 50 },
uniqueness: { case_sensitive: true }
end
new.html.erb
<%= f.simple_fields_for :title do |title| %>
<%= title.input :name, label: "Title" %>
<% end %>
employees_controller.rb
def create
if EmployeeTitle.exists?(name: employee_params[:title_attributes][:name])
# find title and use it?
else
#employee = current_user.employee.build(employee_params)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
Edit: Using first_or_create
def create
EmployeeTitle.where(name: employee_params[:title_attributes][:name]).first_or_create do |title|
#employee = current_user.employees.build(employee_params, :title => title)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
This makes the #employee go out of scope. Error: Undefined method `save' for nil:NilClass.
In addition, if I do this, won't the title be created regardless of whether the rest of the employee data is valid?
Using private method
employee.rb
private
def title_attributes=(attributes)
self.title = EmployeeTitle.find_or_create_by_name(name: attributes[:name])
end
The value is not being set. I get a "cannot be blank" validation error. The parameters include
employee: !ruby/hash:ActionController::Parameters
title: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
name: Consultant
The !ruby/hash:ActiveSupport::HashWithIndifferentAccess was not there before.
employee_params
private
def employee_params
params.require(:employee).permit(
title_attributes: [:id, :name],
)
end
What you need to do is to change this:
def create
if EmployeeTitle.exists?(name: employee_params[:title_attributes][:name])
# find title and use it?
else
#employee = current_user.employee.build(employee_params)
end
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
with this:
def create
#employee = current_user.employee.build(employee_params)
if #employee.save
flash[:success] = "Employee #{#employee.title.name} created."
redirect_to #employee
else
render 'new'
end
end
Now, override title_attributes method by putting this code in your app/models/employee.rb file:
def title_attributes=(attributes)
self.title = EmployeeTitle.find_or_create_by_name(attributes[:name])
end
Now, every time you'll create an employee whose name already exists with the particular name, it'll be used by default for associating it as title. Let the controller be skinny as it used to be.
Read more about find_or_create_by method here.
However, your question's title says: Rails 4, but you have tagged ruby-on-rails-3.2. If you're using Rails 4 then you can use this instead:
EmployeeTitle.find_or_create_by(name: attributes[:name])

Rails custom validate - uniqueness of a property across models

I'm stuck at defining a custom validation method that's purpose is to verify uniqueness of a property across two models
I realize this is bad code, but i wanted to get the test passing before refactor
here is a model with the custom validation to check another model property, error undefined local variable or method `params' (be gentle I'm still trying to figure out RoR)
class Widget < ActiveRecord::Base
include Slugable
validates :name, presence: true
validate :uniqueness_of_a_slug_across_models
def uniqueness_of_a_slug_across_models
#sprocket = Sprocket.where(slug: params[:widget_slug]).first
if #sprocket.present?
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
end
end
You don't have access to params in a model. It belongs to controller and view. What you could do is to call custom method in widgets controller (instead of regular save) in order to pass params to a model:
class WidgetsController < ActionController::Base
def create
#widget = Widget.new(widget_params)
if #widget.save_with_slug_validation(params)
redirect_to widgets_path
else
render :new
end
end
end
and define it:
class Widget < ActiveRecord::Base
# ...
def save_with_slug_validation(params)
sprocket = Sprocket.find_by(slug: params[:widget_slug])
if sprocket
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
save
end
end
I didn't test it but it should work.
P.S. Rails 4 style is used.
UPD
I should have tested it, sorry. Please use another approach.
Widgets controller:
# POST /widgets
# POST /widgets.json
def create
#widget = widget.new(widget_params)
#widget.has_sprocket! if Sprocket.find_by(slug: params[:widget_slug])
respond_to do |format|
if #widget.save
format.html { redirect_to [:admin, #widget], notice: 'widget was successfully created.' }
format.json { render action: 'show', status: :created, location: #widget }
else
format.html { render action: 'new' }
format.json { render json: #widget.errors, status: :unprocessable_entity }
end
end
end
Widget model:
class Widget < ActiveRecord::Base
include Slugable
validates :name, presence: true
validate :uniqueness_of_a_slug_across_models, if: 'has_sprocket?'
def uniqueness_of_a_slug_across_models
errors.add(:uniqueness_of_a_slug_across_models, "can't be shared slug")
end
def has_sprocket!
#has_sprocket = true
end
def has_sprocket?
!!#has_sprocket
end
end
It would be better to move has_sprocket! and has_sprocket? methods and maybe validation itself to Slugable concern.

Subform for parent object

So I've been holding off putting a question on here because I don't want to bother the community with stupid questions, but I'm going to ask for help now anyway.
I'm quite new to Ruby on Rails, and as you've probably read from the title, I'm having trouble with my subform. More specifically, with assigning the parent object to a client object. I'm building a system for my work in where employees can register repairs (mobile phones) and keep track of them. I'm building the client object with #repair = Repair.new, which works fine, but when I try to set the Client with #repair = Client.new, the :client_id on the repair stays null.
Here's my repair.rb: (some fields are in Dutch, please ignore that)
class Repair < ActiveRecord::Base
attr_accessible :imei, :klantnaam, :telefoon, :intake, :branch_id, :id, :client_id
attr_accessible :merk, :type, :batterij, :lader, :headset, :batterijklep, :carkit, :schade_toestel, :schade_scherm, :bon, :datum_bon, :klacht, :prijsindicatie
belongs_to :branch
belongs_to :client
accepts_nested_attributes_for :client
end
client.rb:
class Client < ActiveRecord::Base
attr_accessible :email, :firstname, :lastname, :number, :phone, :postalcode
has_many :repairs
end
repairs_controller.rb: (I've left the irrelevant methods out, I was getting tired of the 4 spaces :P)
class RepairsController < ApplicationController
# GET /repairs/new
# GET /repairs/new.json
def new
#repair = Repair.new
#repair.client = Client.new
if request.remote_ip == "xx.xx.xx.xx"
#repair.branch = Branch.where(:name => "Xxxxxxx").first
end
#repair.intake = Time.now
respond_to do |format|
format.html # new.html.erb
format.json { render json: #repair }
end
end
# POST /repairs
# POST /repairs.json
def create
#repair = Repair.new(params[:repair])
respond_to do |format|
if #repair.save
format.html { redirect_to #repair, notice: 'Repair was successfully created.' }
format.json { render json: #repair, status: :created, location: #repair }
else
format.html { render action: "new" }
format.json { render json: #repair.errors, status: :unprocessable_entity }
end
end
end
end
And this is the JSON I get from /repair/new.json:
{"batterij":null,"batterijklep":null,"bon":null,"branch_id":null,"carkit":null,"client_id":null,"created_at":null,"datum_bon":null,"headset":null,"id":null,"imei":null,"intake":"2013-02-01T23:29:10Z","klacht":null,"klantnaam":null,"lader":null,"merk":null,"pickup":null,"prijsindicatie":null,"schade_scherm":null,"schade_toestel":null,"telefoon":null,"updated_at":null}
By the way, the branch assignment works flawlessly... (It's null now because I'm not on the IP I specified in the new method)
Please help me out... :-(
Robin
Solved it!!
The code above all works flawlessly, the problem was a <% instead of <%= in my view, which made my subform not show up. Duhh.

Resources