Class Variables instantiated(refreshed) for each new instance - ruby-on-rails

I have two classes Category and order_item, where an order_item belongs_to a category.
I want to add unique 'category' items to a class variable that is an array of 'categories' and is represented by an array class variable ##categoriesList, each time a new category is detected among the instance variables.
This is what i tried.
class OrderItemsController < ApplicationController
##categoriesList = []
def create
#order_item = OrderItem.new(order_item_params)
if #order_item.save
#total = #order_item.order.total
#orderItemCategory = #order_item.category
if #orderItemCategory.set?
if !(##categoriesList.include? #orderItemCategory)
#total += #orderItemCategory.price
##categoriesList.push(#orderItemCategory)
end
........
........
end
Code Explanation:
I don't want the price of the next instance of order_item to be taken in account, if the price of a previous order_item that belongs to a same category's price has already been taken in account for.
For example: Egg and Milk both belong to Combo-1. So i just want to take into account the price of Combo-1 once, and not for each order_item instance, i.e. egg and milk, which would double the total amount.
What I tried:
I push the category name of the order_item after it's price has been taken in account. And when the next order_item is created, i checked if that order_item's category's price has already been recorded by checking it in the current ##categoriesList class variable.
Problem:
At each instance when i check the class variable ##categoriesList, it returns an empty array list, and no previous records that have been pushed to that array is shown.
I want something like a static variable in java, where every instance of the class shares the same variable, without actually refreshing the data in the variable for each instance.

You don't want a class variable pretty much ever since they are not thread safe or actually persistent across requests which really applies to almost any programming language. Every time the code is reloaded the class variable will be reset.
What your trying to do can really be done by modeling the domain properly. In the case of a checkout system its been done a billion times already and the common pattern is:
class Order < ApplicationRecord
has_many :line_items
has_many :products, through: :line_items
end
# rails g model line_item quantity:decimal unit_price:decimal order:belongs_to product:belongs_to
class LineItem < ApplicationRecord
belongs_to :order
belongs_to :product
end
class Product < ApplicationRecord
has_many :line_items
has_many :orders, through: :line_items
end
On the line items table which represents an actual line on an order form you store what order the item belongs to, quantity & the unit price at the time of purchase. To tally an order you sum the line items:
class LineItem < ApplicationRecord
# ...
def net
quantity * unit_price
end
end
class Order < ApplicationRecord
# ...
def net
line_items.sum(&:net)
end
end
So then your can just call order.net and it will give you the net sum. I have no clue where you're going with that categories mess but if we looked up the price here by going to the product we would not be able to account for past transactions unless the prices are completely static.
This is how you would handle creating line items:
resources :orders do
resources :line_items
end
class LineItemsController < ApplicationController
before_action :set_order
# GET /orders/1/line_items
def new
#line_item = #order.line_items.new
#products = Product.all
end
# POST /orders/1/line_items
def create
#line_item = #order.line_items.new(line_item_params) do |items|
items.unit_price = li.product.price # stores the price at time of purchase
end
if #line_item.save
redirect_to #order
else
#products = Product.all
render :new
end
end
# ...
private
def line_item_params
params.require(:line_item)
.permit(:quantity, :product_id)
end
def set_order
#order = Order.find(params[:order_id])
end
end
<%= form_with(model: [#order, #line_item], local: true) do |f| %>
<div class="field">
<%= f.label :product_id %>
<%= f.collection_select :product_id, #products, :id, :name %>
</div>
<div class="field">
<%= f.label :quantity %>
<%= f.number_field :quantity %>
</div>
<%= f.submit %>
<% end %>

Related

Creating X amount of new database rows using input X from a form in Rails with auto-incrementing name

I am working on my first rails app in which hotel owners can add types of rooms (premium, budget, single rooms, double rooms etc.) incl. the # of rooms of that type (room_count).
I am currently working with 3 tables,
(i) hotels table, where the user can create a new hotel,
(ii) accommodation_categories table, where the user can enter a new type of rooms incl. room_count &
(iii) an accommodations table, with the individual rooms per type.
My question is how to use the room_count input of the accommodation_categories table (obtained via a form) can be used to automatically create these room records in my accommodations table
e.g. how to translate the accommodation_category table input room_count into room_count*records of accommodations?
~ I am really sorry if this explanation is too elaborate, but I am not sure how else to explain it given that my technical vocabulary is rather limited ~
routes.rb
Rails.application.routes.draw do
resources :hotels do
resources :accommodation_categories do
resources :accommodations
end
end
hotel.rb
class Hotel < ApplicationRecord
has_many :accommodation_categories, dependent: :destroy
end
accommodation_category.rb
class AccommodationCategory < ApplicationRecord
belongs_to :hotel
has_many :accommodations, dependent: :destroy
end
accommodation.rb
class Accommodation < ApplicationRecord
belongs_to :accommodation_category
end
accommodation_categories_controller.rb
def new
#hotel = Hotel.find(params[:hotel_id])
#accommodation_category = AccommodationCategory.new
end
def create
#accommodation_category = AccommodationCategory.new(accommodation_category_params)
#hotel = Hotel.find(params[:hotel_id])
#accommodation_category.hotel = #hotel
#accommodation_category.save
redirect_to hotel_accommodation_categories_path
end
views/accommodation_categories/new.html.erb
<%= simple_form_for [#hotel, #accommodation_category] do |f|%>
<%= f.input :name %>
<%= f.input :room_count %>
<%= f.submit "Save", class: "btn btn-primary" %>
<% end %>
I indeed used the cocoon gem with which you can create multiple nested objects via one form. It works perfectly.
To create multiple objects without details you can use controller lines such as:
#accommodation_category[:accommodation_count].times {#accommodation_category.accommodations.build}

How to override table column value with column value from second table if it exist?

I'm building e-commerce application in which you can set user specific prices for products. If price for specific product for specific user is not set, it will show default product price.
It works fine, but I'm looking for more efficient solution since I'm not happy with current one.
Tables:
users
products (all product info + regular_price)
prices (user_id, product_id, user_price)
Models:
class User < ApplicationRecord
has_many :prices
end
class Product < ApplicationRecord
has_many :prices
validates :name, presence: true
def self.with_user_prices(current_user)
Product.joins(
Product.sanitize_sql_array(['LEFT OUTER JOIN prices ON prices.user_id = ?
AND products.id = prices.product_id', current_user])
).select('products.*, prices.user_price')
end
end
class Price < ApplicationRecord
belongs_to :product
belongs_to :user
end
How I get all products with user specific prices in controller:
#products = Product.with_user_prices(current_user)
How I display them in view:
<% #products.each do |product| %>
<%= product.user_price ? product.user_price : product.regular_price %>
<% end %>
As you see I'm currently joining prices table and then in view I display user_price (prices table) if it exists, otherwise regular_price (products table).
I would love to solve everything in a single query keeping only one price column with appropriate value according to the current_user
You can make use of SQL COALESCE function:
class Product < ApplicationRecord
# ...
def self.with_user_prices(user)
includes(:prices).where(prices: { user_id: user.id }).select(
'products.*, COALESCE(prices.user_price, products.regular_price) as price'
)
end
end
Then you can use it simply by:
<%= product.price %>
Note that I simplified Product.with_user_prices method a little bit by using includes, which is gonna generate SQL LEFT JOIN query since there's condition on prices.
New Answer:
please do not mark this answer as correct because I just basically extended Marek's code and yours into mine, as after several attempts I came to what you've already done anyway, but I'm just putting it here in case it helps anyone:
app/models/product.rb
class Product < ApplicationRecord
def self.with_user_prices(user)
joins(
sanitize_sql_array([
"LEFT OUTER JOIN prices on prices.product_id = products.id AND prices.user_id = ?", user.id
])
).select(
'products.*',
'COALESCE(prices.user_price, products.regular_price) AS price_for_user'
)
end
end
controller:
#products = Product.with_user_prices(current_user)
view:
<% #products.each do |product| %>
<%= product.price_for_user %>
<% end %>
Old Answer (Inefficient Code):
Untested but can you try the following? (Not sure if this is more or less efficient than your approach)
app/models/product.rb
class Product < ApplicationRecord
has_many :prices
def price_for_user(user)
prices.includes(:user).where(
users: { id: user.id }
).first&.user_price || regular_price
end
end
controller:
# will perform LEFT OUTER JOIN (to eager load both `prices` and `prices -> user`) preventing N+1 queries
#products = Product.eager_load(prices: :user)
view:
<% #products.each do |product| %>
<%= product.price_for_user(current_user) %>
<% end %>

How to create multiple children objects after the parent has been created?

This is my scenario:
I have a an Order model and a Item model. They have the following relationship:
class Order < ApplicationRecord
has_many :items
end
class Item < ApplicationRecord
belongs_to :order
end
In my project, initially, I need to create the Order without Items. After that I need to create the items related to that order.
I have already tried user nested_attributes, however, I'm gonna need to created items more than once and in the second time try the Items I have already created shows up in the form for editing.
Any suggestions on the best approach?
EDIT:
Add one more info. I need the option to create multiple items at once.
An option could be to create first your Order, and then the items.
# config/routes
...
resources :orders do
resources :items
end
# app/controllers/itesm_controller.rb
class ItemsController < ApplicationController
def new
order = Order.find(params[:order_id]
#item = order.items.new
end
def create
item.create(item_params)
redirect_to orders_path # just guessing your paths
end
protected
def item_params
params.require(:item).permit(:your, :attributes, :here)
end
end
# Assuming Rails +5.1
# app/views/items/_form.html.erb
# you can use this partial in 'new.html.erb' and 'edit.html.erb'
<%= form_view model: #item do |form| %>
<%= form.label :your_attribute %>
<%= form.text_field :your_attribute %>
<%= form.submit %>

Rails 4 Inventory Application: database design and use of nested forms

I built this application and it works nicely and is quite simple: https://github.com/ornerymoose/DeviceCount . It allows you to create a new entry for a device where you specify a count (ie, inventory amount) of a device.
Now even though this works, I've been told that it needs to be on a 'per location' basis. Ie, you create an entry and you will have 10 textfields (if there are indeed 10 devices. This amount will never change nor will the devices change) for devices, and for each device text field, you will enter a count for that device. You will choose location for a dropdown menu. When that entry is created, you will have:
-1 location
-10 Devices listed, all with their own count.
I'm struggling wrapping my head around how to design these models. Should I have an Entry and Device model? A separate Count model?
Would a nested form be the best approach here?
Any and all input is appreciated.
Sounds like you'd be best with an Inventory join model (with has_many :through):
#app/models/inventory.rb
class Inventory < ActiveRecord::Base
# id | device_id | location_id | qty | created_at | updated_at
belongs_to :device
belongs_to :location
end
#app/models/device.rb
class Device < ActiveRecord::Base
has_many :inventories
has_many :locations, through: :inventories
accepts_nested_attributes_for :inventories
end
#app/models/location.rb
class Location < ActiveRecord::Base
has_many :inventories
has_many :devices, through: :inventories
end
This will allow you to set the "quantity" of the device for each location (will have to use accepts_nested_attributes_for):
#app/controllers/devices_controller.rb
class DevicesController < ApplicationController
def new
#device = Device.new
#locations = Location.all
end
def create
#device = Device.new device_params
#device.save
end
private
def device_params
params.require(:device).permit(inventories_attributes: [:qty])
end
end
#app/views/devices/new.html.erb
<%= form_for #device do |f| %>
<%= f.text_field :name %>
<%= f.fields_for :inventories, Location.all do |i| %>
<%= i.number_field :qty %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to create a new Device and have it's qty available through its Inventory.

Ruby on Rails - how to belong to many?

I have a table called Report, and another called Phases. Let's say I have 10 phases and I want to have checkboxes on the report form that lists out all the phases. How can I collect the items that are checked and store that data in the database?
Do I need to have columns such as phase_one:integer phase_two:integer phase_three:integer and just pull back the ones that aren't null? Or can I somehow store the IDs of x phases in one column and pull back those IDs in an array?
To clarify NateSHolland's answer, you can use has_and_belongs_to_many if the phases are predefined:
#app/models/report.rb
class Report < ActiveRecord::Base
has_and_belongs_to_many :phases
end
#app/models/phase.rb
class Phase < ActiveRecord::Base
has_and_belongs_to_many :reports
end
This will allow you to populate the phase_ids (collection_singular_ids) attribute from your form:
#config/routes.rb
resources :reports
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def new
#report = Report.new
#phases = Phase.all
end
def create
#report = Report.new report_params
#report.save
end
private
def report_params
params.require(:report).permit(:phase_ids)
end
end
#app/views/reports/new.html.erb
<%= form_for #report do |f| %>
<%= f.collection_check_boxes :phase_ids, #phases, :id, :name %>
<%= f.submit %>
<% end %>
This would allow you to specify which phases your report has, although it will not have any changes.
What I think you'd be better doing is using has_many :through, allowing you to define which phase you're referencing:
#app/models/report.rb
class Report < ActiveRecord::Base
has_many :progress
has_many :phases, through: :progress
accepts_nested_attributes_for :progress #-> define level
end
#app/models/progress.rb
class Progress < ActiveRecord::Base
#id report_id phase_id phase_lvl created_at updated_at
belongs_to :report
belongs_to :phase
end
#app/models/phase.rb
class Phase < ActiveRecord::Base
has_many :progress
has_many :reports, through: :progress
end
The important factor here is the phase_lvl column - it's my understanding that you have certain "levels" which your phases will be a part of. I can't describe it properly, but to give you context, you'll have something like...
Report is written (phase 5)
Report is published (phase 3)
Report is Sent (phase 15)
The above will give you the ability to define which "level" each phase denotes. I think this is the distinction you're looking for.
This would be a little more tricky to implement, but worth it:
#app/controllers/reports_controller.rb
class ReportsController < ApplicationController
def new
#report = Report.new
#phases = Phase.all
10.times do
#report.progress.build
end
end
def create
#report = Report.new report_params
#report.save
end
private
def report_params
params.require(:report).permit(progress_attributes: [:phase_id, :phase_lvl])
end
end
This will give you the ability to define the following:
#app/views/reports/new.html.erb
<%= form_for #report do |f| %>
<%= f.fields_for :progress do |p| %>
<%= p.collection_select :phase_id, #phases, :id, :name %>
<%= p.number_field :phase_lvl %>
<% end %>
<%= f.submit %>
<% end %>
It sounds like what you may want is a many to many relationship or a has_and_belongs_to_many. To do this you will need to create a join table between Report and Process that will have a report_id column and a phase_id. For more information check out this documentation: http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
In the models you will need to add:
Class Report < ActiveRecord::Base
has_and_belongs_to_many :phases
...
end
and
Class Phase < ActiveRecord::Base
has_and_belongs_to_many :reports
...
end

Resources