Calculating and inserting value into table column using rails - ruby-on-rails

Hi I'm a newbie to Rails. So pardon if this is a silly question.
I'm working on a project where I need to calculate the Bank Balance and Cashbox balance in a Transaction. So this depends upon the type of transaction "Debit/Credit" and type of payment "Online Payment/Cheque/Cash". There is only one model involved and that is Transaction Model and transactions table. So this is what I'm doing,
transactions_controller
def create
#transaction = Transaction.create(transaction_params)
#amount = transaction_params[:amount].to_f
#cashbox = transaction_params[:cashbox].to_f
#bank = transaction_params[:bank].to_f
if transaction_params[:t_type] == "Debit"
if transaction_params[:t_method] == "Cash"
#cashbox -= #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank -= #amount
transaction_params[:bank] = #bank.to_s
end
elsif transaction_params[:t_type] == "Credit"
if transaction_params[:t_method] == "Cash"
#cashbox += #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank += #amount
transaction_params[:bank] = #bank.to_s
end
end
if #transaction.save
redirect_to #transaction
else
render 'new'
end
end
def transaction_params
params.require(:transaction).permit(:date, :notes, :t_type, :t_method, :amount, :paid_by, :paid_to, :cashbox, :bank, :house_id, :expense_for)
end`
Transaction Model
class Transaction < ActiveRecord::Base
end
But when I submit the form containing all the values, the calculation part does't happen and rails inserts only the values submitted in the form.
How to calculate cashbox balance and bank balance on submit from the form and store the updated values in the table?
Also, kindly suggest if there are any better ways to do this.

You need to put this functionality to the model and update object before rendering (if i understood you right). Now you never save the changes into the object.

Your logic is confuse, let read more resful of rails.
- First: you can't save, after that create Transaction again.
- Second: you created transaction and below don't see updating that transaction should of course it be not changed.
Let try yourself:
def create
#amount = transaction_params[:amount].to_f
#cashbox = transaction_params[:cashbox].to_f
#bank = transaction_params[:bank].to_f
if transaction_params[:t_type] == "Debit"
if transaction_params[:t_method] == "Cash"
#cashbox -= #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank -= #amount
transaction_params[:bank] = #bank.to_s
end
elsif transaction_params[:t_type] == "Credit"
if transaction_params[:t_method] == "Cash"
#cashbox += #amount
transaction_params[:cashbox] = #cashbox.to_s
else
#bank += #amount
transaction_params[:bank] = #bank.to_s
end
end
#transaction = Transaction.new transaction_params
if #transaction.save
redirect_to #transaction
else
render 'new'
end
end
def transaction_params
params.require(:transaction).permit(:date, :notes, :t_type, :t_method, :amount, :paid_by, :paid_to, :cashbox, :bank, :house_id, :expense_for)
end`

You've got a few things in a bit of a muddle I think:
1) if #transaction.save
Here you're trying to save the transaction and only continuing if you do so successfully. Where is #transaction getting set up though? Presumably in some kind of before_filter but I suspect you don't need that in this case.
2) #transaction = Transaction.create(transaction_params)
Now you're creating, and saving to the database, a new Transaction object base on the parameters that are being submitted from the form (but only if it passes validation).
3) Then you do a load of work to calculate some parameters that appear to be transaction parameters. At this point though you've already created the transaction object with the parameters that were submitted. You add all these to the params hash which is predominantly used to pass web request parameters in (yes, you can use it for other things but I'd avoid doing that just yet).
Now, the most simple fix is probably to move your Transaction.create line to after all your params changes because then you'll be creating a transaction with the parameters you want. I wouldn't advocate that though. It seems like this code would be better moved to the Transaction model and have a method to apply the values to a transaction after you've built it.
(I'd also question if you've got your modelling quite right. You seem to be passing in a #bank value as part of the web request and then applying the value of the transaction your creating to it to give your bank value on the transaction. Of course, I'm not sure of your exact requirements but this seems like a potentially problematic approach. One for a different question perhaps).

Related

Iterating through only persisted objects

This is the code for my create display:
def create
#display = #department.displays.new(display_params)
#token = #display.build_token(value: (max_token + 1) , status: 0)
if #display.save
....
end
max_token is the method called to find the largest number of token in the display tokens.
def max_token
#tokens = #department.displays.map do |display|
display.token.value
end
#tokens.max
end
Problem
I've created a new display for the department with the code in the create method.
#display = #department.displays.new(display_params)
But it is not saved yet, as the #display.save is called only after the max_token method.
But when the max_token method is called, the code
#tokens = #department.displays.map do |display|
is also displaying the unsaved display of the department.
And since the token of the display has not been set yet, as it is not saved, throws a nil value error.
My Solution
This is what i've tried upto now, but I want to know if there's a better method.
def max_token
#tokens = #department.displays.map do |display|
if display.token.nil?
display.token.value
else
0
end
end
#tokens.max
end
If you're not worried about the uniqueness of value at the DB layer, you can simply filter out displays with a nil value for token:
def max_token
#department.displays.where.not(token: nil).map do |display|
display.token.value
end.max
end
(This is also assuming you don't actually need to assign #tokens as a side effect of max_token.)
Try to create a new separated Display first then assign it to the #department after max_token get called so the new Display won't be included in #department.displays.map
def create
#display = Displays.new(display_params)
#token = #display.build_token(value: (max_token + 1) , status: 0)
#department.displays << #display
if #display.save
....
end

How find the distance between two objects?

Im using geocode. The idea is our partners can post products with an address. When they do so it fetches the latitude and longitude. Now when our customers go to buy that product they have to enter in a delivery address to tell us where to deliver the product. However if they delivery address is not within 20 miles of the product they are not allowed to get the product delivered.
Im getting an error message saying this "undefined method `latitude' for nil:NilClass"
Like I said the product.longitude, product.latitude is already set when the users are trying to order.
Not sure if it's because the order.delivery_address(lat, long) is not submitted into the database yet and its trying to check the distance. Here my code below
So My question is how can is how can i find the distance between the product address and order address and I want to show a alert message to the user if the distance between the two is over 20 miles.
def create
product = Product.find(params[:product_id])
if current_user == product.user
flash[:alert] = "You cannot purchase your own property"
elsif current_user.stripe_id.blank? || current_user.phone_number.blank?
flash[:alert] = " Please update your payment method and verify phone number please"
return redirect_to payment_method_path
elsif Geocoder::Calculations.distance_between([product.latitude, product.longitude], [#order.latitude, #order.longitude]) < 20
flash[:alert] = "The delivery address you provided is outside the delivery zone. Please choose a different product."
else
quantity = order_params[:quantity].to_i
#order = current_user.orders.build(order_params)
#order.product = product
#order.price = product.price
#order.total = product.price * quantity + product.delivery_price
# #order.save
if #order.Waiting!
if product.Request?
flash[:notice] = "Request sent successfully... Sit back and relax while our licensed dispensary fulfil your order :)"
else
#order.Approved!
flash[:notice] = "Your order is being filled and it will delivered shortly:)"
end
else
flash[:alert] = "Our licensed dispensary cannot fulfil your order at this time :( "
end
end
redirect_to product
end
You set #order in the following line:
#order = current_user.orders.build(order_params)
But you try to call its longitude and latitude methods above this, before you even set #order variable. To simply fix this problem, you can move this line up, it can even be located at the beginning of create method, since it doesn't depend on product or anything like that:
def create
#order = current_user.orders.build(order_params)
# ...
end
Although, there are number of problems in your code, like method names starting with capital letters (you can do it, but you shouldn't, it's against the convention) or overall complexity of the method.
You should move the business logic to the model where it belongs.
So lets start by creating a validation for the product distance:
class Order < ApplicationRecord
validates :product_is_within_range,
if: -> { product.present? } # prevents nil errors
# our custom validation method
def product_is_within_range
errors.add(:base, "The delivery address you provided is outside the delivery zone. Please choose a different product.") if product_distance < 20
end
def product_distance
Geocoder::Calculations.distance_between(product.coordinates, self.coordinates)
end
end
Then move the calculation of the total into the model:
class Order < ApplicationRecord
before_validation :calculate_total!, if: ->{ product && total.nil? }
def calculate_total!
self.total = product.price * self.quantity + product.delivery_price
end
end
But then you still have to deal with the fact that the controller is very broken. For example:
if current_user == product.user
flash[:alert] = "You cannot purchase your own property"
Should cause the method to bail. You´re not actually saving the record either. I would start over. Write failing tests for the different possible conditions (invalid parameters, valid parameters, user is owner etc) then write your controller code. Make sure you test each and every code branch.

Rails - how to set default values in a model for quantity

I'm trying to allow a User to book events for more than one space at a time, so if one space at an event costs £10 and a User wants to book four spaces then they would need to pay £40.
I've implemented a method in my Booking model to cater for this -
Booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
def reserve
# Don't process this booking if it isn't valid
return unless valid?
# We can always set this, even for free events because their price will be 0.
self.total_amount = quantity * event.price_pennies
# Free events don't need to do anything special
if event.is_free?
save
# Paid events should charge the customer's card
else
begin
charge = Stripe::Charge.create(amount: total_amount, currency: "gbp", card: #booking.stripe_token, description: "Booking number #{#booking.id}", items: [{quantity: #booking.quantity}])
self.stripe_charge_id = charge.id
save
rescue Stripe::CardError => e
errors.add(:base, e.message)
false
end
end
end
end
When I try to process a booking I get the following error -
NoMethodError in BookingsController#create
undefined method `*' for nil:NilClass
This line of code is being highlighted -
self.total_amount = quantity * event.price_pennies
I need to check/make sure that quantity returns a value of 1 or more and event.price_pennies returns 0 if it is a free event and greater than 0 if it is a paid event. How do I do this?
I did not set any default values for quantity in my migrations. My schema.rb file shows this for price_pennies -
t.integer "price_pennies", default: 0, null: false
This is whats in my controller for create -
bookings_controller.rb
def create
# actually process the booking
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if #booking.reserve
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
end
So, do I need a method in my booking model to rectify this or should I do a validation for quantity and a before_save callback for event?
I'm not quite sure how to do this so any assistance would be appreciated.
Just cast to integer, in this case you seem to be done:
self.total_amount = quantity.to_i * event.price_pennies.to_i
Migrations are used to modify the structure of you DB and not the data.
In your case I think you need to seed the DB with default values, and for that purpose you use 'db/seeds.rb' file which is invoked once every time your application is deployed.
You would do something like this in seeds.rb
Booking.find_or_create_by_name('my_booking', quantity:1)
So when the application is deployed the above line of code is executed. If 'my_booking' exists in the table then nothing happens, else it will create a new record with name='my_booking' and quantity=1.
In your localhost you'll execute 'rake db:seed' to seed the DB.

what var type to dynamically access Model's attribute from another controller? (Rails 4.2)

Goal: dynamically update another Model's properties (Tracker) from Controller (cards_controller.rb), when cards_controller is running the def update action.
Error I receive : NameError in CardsController#update, and it calls out the 2nd last line in the
def update_tracker(card_attribute) :
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
Perceived problem: I have everything working except that I don't know the appropriate way to 'call' the attribute of Tracker correctly, when using dynamic attributes.
The attribute of the Tracker is an array (using PG as db works fine), I want to
figure out what property has been changed (works)
read the corresponding property array from Tracker's model, and make a local var from it. (works I think, )
push() a new array to the local var. This new array contains the datetime (of now) and, a string (with the value of the updated string of the Card) (works)
updated the Tracker with the correct attribute.
With the following code from the cards_controller.rb
it's the if #card.deck.tracked in the update method that makes the process start
cards_controller.rb
...
def update
#card = Card.find(params[:id])
if #card.deck.tracked
detect_changes
end
if #card.update_attributes(card_params)
if #card.deck.tracked
prop_changed?
end
flash[:success] = "Card info updated."
respond_to do |format|
format.html { render 'show' }
end
else
render 'edit'
end
end
...
private
def detect_changes
#changed = []
#changed << :front if #card.front != params[:card][:front]
#changed << :hint if #card.hint != params[:card][:hint]
#changed << :back if #card.back != params[:card][:back]
end
def prop_changed?
#changed.each do |check|
#changed.include? check
puts "Following property has been changed : #{check}"
update_tracker(check)
end
end
def update_tracker(card_attribute)
tracker_attribute = case card_attribute
when :front; :front_changed
when :back; :back_changed
when :hint; :hint_changed
end
string_tracker_column = tracker_attribute.to_s
#tracker ||= Tracker.find_by(card_id: #card.id)
updated_array = #tracker.instance_variable_get("#{string_tracker_column}")[Time.zone.now, #card.(eval(card_attribute.to_s))]
#tracker.update_attribute(tracker_attribute, updated_array)
end
Edit: For clarity here's the app/models/tracker.rb:
class Tracker < ActiveRecord::Base
belongs_to :card
end
Your use of instance_variable_get has been corrected, however this approach is destined to fail because ActiveRecord column values aren't stored as individual instance variables.
You can use
#tracker[string_column_changed]
#card[card_attribute]
To retrieve attribute values by name. If you want to get an association, use public_send. The latter is also useful if there is some accessor wrapping the column value (eg carrierwave)
From your error it seem your issue is this:
#tracker.instance_variable_get("#{string_tracker_column}")
evaluates to this after string interpolation:
#tracker.instance_variable_get("front_changed")
which is incorrect use of instance_variable_get. It needs an # prepended:
#tracker.instance_variable_get("#front_changed")
Seems like using instance_variable_get is unnecessary, though, if you set attr_reader :front_changed on the Tracker model.

Ruby on Rails - Undefined methods for NilClass

I'm creating a picture-rating app where users can click on pictures and rate them on a scale from 1 to 5. I'm trying to calculate the average rating of a picture. Before when users clicked on a rating value, that value became the picture's rating.
Rating: 5
If a user clicked on 1, the rating would change to 1
Rating: 1
When reality, the rating should have been 3.
(5 + 1) / 2
=> 3
Here's what I've accomplished so far in implementing this feature.
I added a migration to create two new columns for my Pictures Table
rails g migration AddRatingsToPictures ratings_count: integer, rating_total: integer
Both the new attributes, ratings_count and rating_total are integer types, meaning they are assigned a nil value at default.
p = Picture.first
p.attribute_names
=> ['id', 'title', 'category', 'stars', 'updated_at', 'created_at',
'ratings_count', 'rating_total']
p.ratings_count
=> nil
p.rating_total
=> nil
My only problem is the NilClass Error.
Here is my update method in my PicturesController.
def update
#picture = Picture.find(params[:id])
#picture.ratings_count = 0 if #picture.stars.nil?
#picture.rating_total = #picture.stars
#picture.rating_total += #picture.stars if #picture.stars_changed?
#picture.ratings_count += 1 if #picture.rating_total_changed?
if #picture.update_attributes(picture_params)
unless current_user.pictures.include?(#picture)
#picture = Picture.find(params[:id])
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
Here is my picture_param method in my PicturesController
def picture_params
params.require(:picture).permit(:title, :category, :genre, :stars)
end
Here is what the two new columns do
ratings_count: Calculates the number of times a picture has been rated
rating_total: Calculates the sum of the stars a picture has received
In the above code, I first set the ratings_count to 0 if the picture doesn't have a rating. This means that the picture hasn't been rated yet.
I then need to initially set the rating_total to the number of stars a picture has. If a user changed the star rating, I would add those stars to the rating_total. And if the total increased, that's my cue to increase the number of ratings.
Obviously, to calculate the average, I'd do something like this.
(#picture.rating_total / #picture.ratings_count).to_f
Now, I think I have the right idea but I know why this doesn't work. When columns are created with an integer value, by default they are set to nil. This leads to a NilClass Error when I load the web page.
undefined method `/' for nil:NilClass
Here is my code in the View
<li><strong>Rating:</strong> <%= pluralize((#picture.rating_total / #picture.ratings_count), 'Star') %></li>
Ok, the main reason it is not working is because
you fetch the picture
you check the stars from the database, and the NOT the passed form-parameters
you do update_attributes, which if I am not mistaken, used to set attributes and then save the complete object, but since rails 4 only updates the passed attributes (which is what you would expect)
One small remark: keeping the rating correct is a function I would place in the model, NOT in the controller.
Furthermore, how to handle the if nil, initialise to zero I wrote a short blogpost about. In short: overrule the getter.
So I would propose the following solution. In your model write
class Picture < ActiveRecord::Base
def ratings_count
self[:ratings_count] || 0
end
def ratings_total
self[:ratings_total] || 0
end
def add_rating(rating)
return if rating.nil? || rating == 0
self.ratings_count += 1
self.ratings_total += rating
self.stars = self.ratings_total.to_f / self.ratings_count
self.save
end
def rating
return 0 if self.ratings_count == 0
self.ratings_total.to_f / self.ratings_count
end
and then the code in your controller becomes much cleaner and readable:
def update
#picture = Picture.find(params[:id])
stars = picture_params.delete(:stars)
if #picture.update_attributes(picture_params)
#picture.add_rating stars
unless current_user.pictures.include?(#picture)
current_user.pictures << #picture
redirect_to #picture, :flash => { :success => "Thank you! This picture has been added to your Favorites List" }
else
redirect_to :action => 'index'
flash[:success] = 'Thank you! This picture has been updated'
end
else
render 'edit'
end
end
I first delete the :stars from the parameters, because I do not want to save those, I want to use those for the add_rating. I then try to update_attributes, which will fail if there are any failing validations, and if that is ok, I will add_rating which itself will handle nil or zero correctly. Well granted: I do not know how you handle a "non-rating" (nil? zero?). It is possible a rating of zero should be added, because it will add a rating, but most UI I know do not allow to select 0 as rating, so you might want to change the zero handling.
This will handle the case of uninitialized (nil) values in your attributes...
def update
#picture = Picture.find(params[:id])
if #picture.stars_changed?
#picture.ratings_count = (#picture.ratings_count || 0) + 1
#picture.rating_total = (#picture.rating_total || 0) + ( #picture.stars || 0)
end
You don't need an array of ratings or ratings persisted to database, assuming you only count votes where the rating changes, you can accumulate the count and the total and divide the two (which is, in fact, what you're doing so I'm preaching to the converted).
Although it seems to me that if I change a picture from 5 to 1 and it only changes to 3, I'm gonna keep clicking 1 :)
You could set the default value on the migration when you created it. But no worries, you can create a new migration to change it:
# Console
rails g migration change_default_for_ratings_count_and_rating_total
# Migration Code
class ChangeDefaultForRatingsCountAndRatingTotal < ActiveRecord::Migration
def change
change_column :pictures, :ratings_count, :integer, default: 0
change_column :pictures, :rating_total, :integer, default: 0
end
end
Keep in mind that some databases don't automatically assign newly updated default values to existing column entries, so maybe you will have to iterate over every picture already created with nil values and set to 0.
Ok, an alternative...
Do an after_initialize so the fields are never, never, ever nil. Even if you're creating a new Picture object, they'll be initialized as zero. Problem will go away.
class Picture << ActiveRecord::Base
after_initialize do |picture|
picture.ratings_count ||= 0
picture.rating_total ||= 0
end
...
end

Resources