Error with initial calculation of instance variable values in Rails controller - ruby-on-rails

I'm looking to build a basic app that provides a score based on how enjoyable users found a given lunch.
In my view, the voting buttons look like this:
<%= form_for :lunch, url: upvote_lunch_lunch_path(params[:id]), method: :put, :html => {:class => 'form-inline'} do |f| %>
<%= f.hidden_field :liked %>
<%= f.hidden_field :id %>
<%= f.submit "Like Lunch", class: "btn btn-large btn-success" %>
<% end %>
<%= form_for :lunch, url: downvote_lunch_lunch_path(params[:id]), method: :put, :html => {:class => 'form-inline'} do |f| %>
<%= f.hidden_field :disliked %>
<%= f.hidden_field :id %>
<%= f.submit "Dislike Lunch", class: "btn btn-large btn-danger" %>
<% end %>
Displaying the value (in a partial) looks like this:
<%= number_to_percentage(#enjoy_score * 100, precision: 0, format: "%n") %>
and finally, in my "lunches" controller, I have the following:
def show
#lunch = Lunch.find(params[:id])
#provider = #lunch.provider
#total_enjoy_votes = (#lunch.liked + #lunch.disliked)
#enjoy_score = (#lunch.liked.to_f / #total_enjoy_votes)
end
def downvote_lunch
#lunch = Lunch.find(params[:id])
#lunch.increment!(:disliked)
redirect_to #lunch
end
def upvote_lunch
#lunch = Lunch.find(params[:id])
#lunch.increment!(:liked)
redirect_to #lunch
end
Everything works as expected, so long as the database already has values for liked and disliked against that particular lunch ID. If, for example, you are the first person to attempt to answer this question (lets say just after a lunch was created) the application errors out with the message "undefined method +' for nil:NilClass" at this line:#total_enjoy_votes = (#lunch.liked + #lunch.disliked)`
Two questions:
Why is it if I open the database (with sqlitebrowser) and "seed" a row of data with liked = 1 and disliked = 1 the methods will work as expected, but if I don't, it errors out? What should I do to "initialize" #lunch.liked and #lunch.disliked so that there isn't an error for the initial user?
(Bonus Point) How can I keep the controller code DRY so I don't have to type #lunch = Lunch.find(params[:id])
at the beginning of every method?
Thanks so much in advance. I apologize if this is a insanely simple question.

1.
#total_enjoy_votes = (#lunch.liked + #lunch.disliked) errors, because #lunch.liked is nil, since it is never set to anything, as well, as #lunch.disliked
To avoid this error you should check if liked and disliked are present.
liked = #lunch.liked ? #lunch.liked : 0
disliked = #lunch.disliked ? #lunch.disliked : 0
#total_enjoy_votes = (liked + disliked)
#enjoy_score = (liked.to_f / #total_enjoy_votes)
2.
before_filter :find_lunch!, only: [ :update, :destroy ] # list of actions where to perform the method.
private
def find_lunch!
#lunch = Lunch.find(params[:id])
end
EDIT
explanation to the line: #lunch.liked ? #lunch.liked : 0
It is ternary operator which comes in handy really often.
Syntax
boolean_expression ? true_expression : false_expression
Example
grade = 88
status = grade >= 70 ? "pass" : "fail"
#=> pass
It is the same, as if I wrote something like:
if #lunch.liked.nil?
liked = 0
else
liked = #lunch.liked
end

First approach:-
def show
#lunch = Lunch.find(params[:id])
#provider = #lunch.provider
liked = ((#lunch.liked.present? and #lunch.liked >= 0) ? #lunch.liked : 0)
#total_enjoy_votes = liked + ((#lunch.disliked.present? and #lunch.disliked >= 0) ? #lunch.disliked : 0)
#enjoy_score = (#total_enjoy_votes > 0 and liked > 0) ? (#lunch.liked.to_f / #total_enjoy_votes) : 0
end
Second approach:-
In Lunch model set a calback as
class Lunch < < ActiveRecord::Base
before_save :initialize_liked_disliked
private
def initialize_liked_disliked
if self.new_record?
self.liked = 0
self.disliked = 0
end
end
end
It will initialize both the fields with 0, then make the changes in controller as:
def show
#lunch = Lunch.find(params[:id])
#provider = #lunch.provider
#total_enjoy_votes = #lunch.liked + #lunch.disliked
#enjoy_score = (#total_enjoy_votes > 0) ? (#lunch.liked.to_f / #total_enjoy_votes) : 0
end

Related

rails does create twice when there is no record in a certain model unexpectedly

Guess
it's strange that it certainly occurs when there is no record in the database.
What I tried
I doubted that the cause was actually ajax so I all removed remote:true but it hardly worked.
controller
def create
#sell = current_user.orders.build(sell_params)
#buy = current_user.orders.build(buy_params)
if #sell.save
flash[:success] = 'your order has successfully submitted.'
notify_order(#sell.rate, #sell.amount, #sell.order_type)
#sell.rate.present? ? #sell.update(order_type: 'limit_sell') : #sell.update(order_type: 'market_sell')
fund = Fund.create_with(amount: 0, crypto_address_id: 1)
.find_or_create_by(user_id: current_user.id, kind: #sell.pair.split('_').last)
fund.update(in_use: #sell.rate * #sell.amount) if #sell.order_type == 'limit_sell'
# try to make orders done
market_checker(#sell.pair)
else
if Order.where(order_status: 'done', pair: params[:order][:pair]).present?
#currency_price = Sell.where(trading_status: 'done', currency_id: Currency.find_by_slug(params[:sell][:currency_id]).id)
#currency_price.present? ? #currency_price = #currency_price.last.price : #currency_price = ''
else
flash[:danger] = "Unfortunately, there is no trading right now so that we can't show you the chart"
end
#pair = params[:order][:pair].to_s
render :new
end
if #buy.save
flash[:success] = 'your order has successfully submitted.'
notify_order(#buy.rate, #buy.amount, #buy.order_type)
#buy.rate.present? ? #buy.update(order_type: 'limit_buy') : #buy.update(order_type: 'market_buy')
fund = Fund.create_with(amount: 0, crypto_address_id: 1).find_or_create_by(user_id: current_user.id, kind: #buy.pair.split('_').last)
fund.update(in_use: #buy.rate * #buy.amount) if #buy.order_type == 'limit_buy'
# try to make orders done
market_checker(#buy.pair)
else
if Order.where(order_status: 'done', pair: params[:order][:pair]).present?
#currency_price = Sell.where(trading_status: 'done', currency_id: Currency.find_by_slug(params[:sell][:currency_id]).id)
#currency_price.present? ? #currency_price = #currency_price.last.price : #currency_price = ''
else
flash[:danger] = "Unfortunately, there is no trading right now so that we can't show you the chart"
end
#pair = params[:order][:pair].to_s
render :new
end
end
model
class Order < ApplicationRecord
belongs_to :user
validate :deposited_btc_enough?
validate :insufficient?
validates :amount, format: { with: /\A\d+(?:\.\d{0,8})?\z/ }, numericality: { greater_than: 0.000000009, less_than: 100_000_000 }, presence: true
validate :rate_check
validates :rate, format: { with: /\A\d+(?:\.\d{0,8})?\z/ }, numericality: { greater_than: 0.000000009, less_than: 100_000_000 }, if: :rate_present?
validate :checking_order_type
def rate_present?
rate.present?
end
def checking_order_type
if order_type == 'sell_limit' || order_type == 'buy_limit'
errors.add(:Please, 'specify the rate of your order.') unless rate.present?
end
if order_type == 'sell_market' || order_type == 'buy_market'
errors.add(:You, "can't specify the rate of your order.") if rate.present?
end
end
def deposited_btc_enough?
if rate.present?
if rate.to_d > '0'.to_d && amount > 0
deposited_amount = user.fund.amount if user.fund.amount.present?
amount = rate.to_d * amount.to_s.to_d
if deposited_amount.present?
if coin_to_satoshi(amount).to_i > coin_to_satoshi(deposited_amount).to_i
errors.add(:Your, 'deposited yen is short to order.')
end
else
errors.add(:You, "haven't deposited yen yet or Your transaction hasn't confirmed yet.")
end
else
errors.add(:You, "can't specify negative numbers here.")
end
end
end
def to_param
uuid
end
def insufficient?
if amount.present? && order_type.split('_').last == 'sell'
if CurrencyUser.find_by(user_id: user.id, currency_id: Pair.find_by(name: pair).currency_id).amount < amount
errors.add(:amount, 'of this order is more than your holdings.')
errors[:base] << 'Errors that are related to this order exist.'
end
end
end
def is_number?(string)
true if Float(string)
rescue StandardError
false
end
def rate_check
if rate.present?
errors.add(:You, 'can only specify numbers') unless is_number?(rate)
errors.add(:You, "can't specify the rate as a negative number.") unless '0'.to_d < rate.to_d
end
end
end
form
<%= form_for(#sell,remote:true) do |f| %>
<% if #sell.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<h2><%= #sell.errors.count %>error(s) exist(s).</h2>
<ul>
<% #sell.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.hidden_field :pair,value:#pair%>
<%= f.hidden_field :order_type,value:"market_sell"%>
<p>amount whatever you want to sell like 100</p>
<div class="input-group">
<%= f.number_field :amount, class: 'form-control form-group',placeholder:"amount"%>
</div>
<%= f.submit 'done', class: "btn btn-large bg-info" %>
<% end %><br>
<%= form_for(#buy,remote:true)do |f| %>
<% if #buy.errors.any? %>
<div id="error_explanation" class="alert alert-danger">
<h2><%= #buy.errors.count %>error(s) exist(s).</h2>
<ul>
<% #buy.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
Thanks
Your create method has way too much complexity and I'm assuming you have not written this method with test driven development because you would have broken it down into smaller methods if you had, likely moving more of the logic to the model. I also hope this is not how your code style looks in real life. At first glance, I'm assuming your problem is this:
def create
# why are you creating 2 orders?? Can you change this:
#sell=current_user.orders.build(sell_params)
#buy=current_user.orders.build(buy_params)
#to something more like:
#order = order_from_params
#...omitting rest of this code because it needs a lot of refactoring.
end
private
def order_from_params
#you should probably only deal with 1 order at a time either buy or sell
# you must have a field in orders table to indicate buy or sell, probably int?
if sell_params.present?
#order = current_user.orders.build(sell_params)
else
#order = current_user.orders.build(buy_params)
end
end
Then you'll need to simplify the logic in create method since you only have one model to deal with. You should NOT rely on nor do 2 if #order.save inside the create action since you should again only be creating 1 order at a time. This should get you headed in the right direction. And if you're not doing test driven development, you're gonna have a lot worse problems then what you have already.

ActiveRecord::StatementInvalid in Show Controller

I've got a simple search form, in rails 4 app, that needs two params passed to be able to show relevant data.
I'm getting an 'Mysql2::Error: Unknown column 'data inputted' but the columns do exist. If I instead of '#search = Page.where(params[:one] && params[:two])' use '#search = Page.all' the data shows, but all of it shows.
Form
<%= form_tag(page_show_path, id: "search-form") do %>
<%= text_field_tag :one, params[:one], placeholder: "One" %>
<%= text_field_tag :two, params[:two], placeholder: "Two" %>
<%= submit_tag "Search", :name => nil %>
<% end %>
Model
def self.one(query)
where("one = ?", "%#{query}%")
end
def self.two(query)
where("two = ?", "%#{query}%")
end
Controller
def show
if (params[:one] && params[:two]).present?
#search = Page.where(params[:one] && params[:two])
else
redirect_to page_path, notice: "Not a valid combination"
end
end
You can Create and Use Scope.
scope :find_one_two, ->(query_one, query_two) { where("one = ? AND two = ? ", query_one, query_two) }
#search = Page.find_one_two(params[:one], params[:two])
OR
You can use.
#search = Page.where("one = ? AND two = ?", params[:one], params[:two])
def show
if (params[:one] && params[:two]).present?
#search = Page.where("one like ? AND two like ? ", "%#{params[:one]}%", "%#{params[:two]}%")
else
redirect_to page_path, notice: "Not a valid combination"
end
end
This may solve your problem.

Rails 4 Cannot access hash in nested form (undefined method `[]' for nil:NilClass)

I've build quite complex form which creates one prescription with many realtions. I am using this syntax in view:
- provide(:title, 'Create prescription')
%h1 Add medicines to prescription
.row
.span6.offset3
= form_for #prescription do |f|
= render 'shared/error_prescription_messages'
%p
= f.hidden_field :patient_id, :value => params[:patient_id]
= f.hidden_field :user_id, :value => current_user.id
= f.fields_for :relations do |builder|
= render 'child_form', :f => builder
%p= f.submit "Submit"
chlid_form is quite simple :
- it=f.options[:child_index].to_i
- n= it.to_s
%h2
= "Medicine ##{it+1}"
= f.hidden_field :medicine_id, :id => "my_medicine_id#{it}"
- if params[:prescription].nil? || params[:prescription][:relations_attributes][n.to_sym][:medicine_name].nil?
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path, :id_element => "#my_medicine_id#{it}"
- else
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path, :id_element => "#my_medicine_id#{it}", :value => params[:prescription][:relations_attributes][n.to_sym][:medicine_name]
= f.label :amount, "Amount of medicine boxes"
= f.number_field :amount, :value => 1
= f.label :daily
= f.number_field :daily, :value => 1
= f.label :period_in_days, "Duration of treatment (in days)"
= f.number_field :period_in_days, :value => 1
So as you can see I'm using f.options[:child_index] to get index of child (0,1,2...) cause I generate multiple items with this particular form. I then put it to variable it and sucessfully use it in :id_element => "#my_medicine_id#{it}" which works PERFECTLY fine (creates my_medicine_id0, my_medicine_id1 ....) Although it doesn't work in this line:
:value => params[:prescription][:relations_attributes][n.to_sym][:medicine_name]
where n is just n=it.to_s.
I though somethings wrong in controller but if I change this line to whatever
:value => params[:prescription][:relations_attributes]**[:'0']**[:medicine_name] or any other integer from 0 to 4 everything works great, but I NEED dynamic change in this one. So I got proof that it DOES work because it generates integer fine here "#my_medicine_id#{it}" but won't work in hash! And when I print the whole hash from params I get this:
{"patient_id"=>"7", "user_id"=>"1", "relations_attributes"=>{"0"=>{"medicine_id"=>"13490", "medicine_name"=>"Locacid 500 mcg/g (0,05%) (1 tuba 30 g)", "amount"=>"0", "daily"=>"1", "period_in_days"=>"1"}, "1"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "2"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "3"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}, "4"=>{"medicine_id"=>"", "medicine_name"=>"", "amount"=>"1", "daily"=>"1", "period_in_days"=>"1"}}}
so to get the values I need it's pretty obvious that
params[:prescription][:relations_attributes][SOME_KIND_OF_INETEGER][:medicine_name] should work, but doesn't.
Controller code:
class PrescriptionsController < ApplicationController
before_action :signed_in_user
before_action :doctor_user, only: [:new, :create]
before_action :pharmacist_user, only: [:update]
def new
#prescription =Prescription.new
5.times { #prescription.relations.build }
end
def create
#prescription = Prescription.new(new_prescription_params)
if #prescription.save
flash[:success] = "Prescription created."
redirect_to #prescription
else
5.times { #prescription.relations.build }
render 'new', :prescription => params[:prescription]
end
end
def show
#prescription = Prescription.find(params[:id])
#medicines = #prescription.medicines.paginate(page: params[:page], :per_page => 10)
end
def update
#prescription = Prescription.find(params[:id])
#patient = Patient.find(params[:patient_id])
if !prescription_expired?(#prescription)
#prescription.realized = 1
if #prescription.save
flash[:success] = "Prescription realized."
redirect_to #patient
else
redirect_to root_url
end
else
flash[:notice] = "Can't realize, prescription expired."
redirect_to #patient
end
end
private
def new_prescription_params
params.require(:prescription).
permit(:patient_id, :user_id, relations_attributes: [:medicine_id, :medicine_name, :amount, :daily, :period_in_days])
end
def doctor_user
redirect_to(root_url) unless current_user.function == "doctor"
end
def pharmacist_user
redirect_to(root_url) unless current_user.function == "pharmacist"
end
def prescription_expired?(presc)
presc.created_at < 1.month.ago
end
def signed_in_user
unless signed_in?
store_location
flash[:notice] = "Please log in."
redirect_to login_url
end
end
end
I run out of ideas so I ask you guys if anyone can help. Thanks.
There is no point in using params in your view since you already assigned those to your models. Also when you rendering your new action, those params doesn't exist as nothing has been send to the server yet. Just get rid of all the values from inputs.
Your partial should look like:
- it=f.options[:child_index].to_i
- n= it.to_s
%h2
= "Medicine ##{it+1}"
= f.hidden_field :medicine_id, :id => "my_medicine_id#{it}"
= f.autocomplete_field :medicine_name, autocomplete_medicine_name_relations_path
= f.label :amount, "Amount of medicine boxes"
= f.number_field :amount
= f.label :daily
= f.number_field :daily
= f.label :period_in_days, "Duration of treatment (in days)"
= f.number_field :period_in_days
If you want your fields to have default value, set default value inside your database.

Referencing and manipulating params after form submit in Rails 4 -- ActiveModel::ForbiddenAttributesError

In Rails 4, we have to change to strong parameters. I haven't figured out how to reference specific parameters yet. I keep running into this error:
ActiveModel::ForbiddenAttributesError in TransactionsController#create
When a Transaction is created, I want to manipulate some of the parameters before creating that new object. I also want to save the id of the Account (sent in the form as a hidden_field_tag).
Here is my form:
#newTransaction-modal.modal.hide.fade style="display: none;"
h3 New Transaction
= form_for Transaction.new do |f|
p Date:
= f.text_field :transaction_date, value: Time.now.strftime("%m/%d/%Y")
p Payee:
= f.text_field :payee
p Category:
= f.select :category, options_for_select(["Phone", "Restaurants", "Gas/Fuel"])
p Memo:
= f.text_field :memo
p Outflow:
= f.text_field :outflow, value: 0
p Inflow:
= f.text_field :inflow, value: 0
= hidden_field_tag :account_id, value: params[:id]
br
a.btn href="#" data-dismiss="modal" Cancel
= f.submit "Create Transaction", class: "btn"
and my Transactions controller:
class TransactionsController < ApplicationController
load_and_authorize_resource
def create
transaction_date = Date::strptime(transaction_params, "%m/%d/%Y")
outflow = params[:transaction][:outflow].include?(".") ? params[:transaction][:outflow].gsub(".", "").to_i : (params[:transaction][:outflow] + "00").to_i
inflow = params[:transaction][:inflow].include?(".") ? params[:transaction][:inflow].gsub(".", "").to_i : (params[:transaction][:inflow] + "00").to_i
#account = Account.find(params[:account][:id])
#transaction = #transaction.new(transaction_params)
#transaction.user_id = current_user.id
#transaction.account_id = #account.id
#transaction.transaction_date = transaction_date
#transaction.outflow = outflow
#transaction.inflow = inflow
#transaction.save
diff = inflow - outflow
new_account_balance = #account.balance + diff
#account.update_attributes(balance: new_account_balance)
redirect_to account_path(#account)
end
private
def transaction_params
params.require(:transaction).permit(:transaction_date, :payee, :account_id, :category, :memo, :inflow, :outflow, :user_id)
end
end
How do I reference the account_id in my controller?
I would allow the params of the account as well. You can add {account_attributes: {:id}} to the transaction_params method and that will allow it. Then you can call transaction_params and get back the hash of params that you are free to modify as you please.

link_to edit page default path?

I'm using Rails3 to build a simple customer information page (in table format). On this page user can edit each customer's detailed information. It's possible for each customer to have multiple records. I use link_to to get to the edit page:
<td class="edit" id="edit">edit
<%= link_to 'edit', :action => :edit, :cust_id => ci.cust_id %>
</td>
edit.html.erb:
<h1>Editing cust</h1>
<%= form_for(#cust_info) do |cust| %>
Customer: <%= cust.text_field :cust_schema %>
<%= cust.submit "Save" %>
<% end%>
In my controller, I have this:
def edit
cust_id = params[:cust_id]
#cust_info = CustInfo.find(:all, :conditions => ["cust_id = ?", cust_id])
end
The customer info page shows right. When I click the edit link, I got the error message:
ActionView::Template::Error (undefined method `cust_info_cust_info_path' for #<#<Class:0x7fb0857ce7b8>:0x7fb0857ca780>):
1: <h1>Editing cust</h1>
2:
3: <%= form_for(#cust_info) do |cust| %>
4: Customer: <%= cust.text_field :cust_schema %>
5: <%= cust.submit "Save" %>
6: <% end%>
app/views/track/edit.html.erb:3:in `_app_views_track_edit_html_erb___102324197_70198065248580_0'
Where does `cust_info_cust_info_path' come from?
EDIT: here is the code in the controller:
class TrackController < ApplicationController
def display
end
def information
end
def customerinfo
#cust_info = CustInfo.find(:all, :order=>"cust_id")
#cust_info.each do |ci|
if ci.upload_freq == 'W'
ci.upload_freq = 'Weekly'
elsif ci.upload_freq == 'M'
ci.upload_freq = 'Monthly'
end
end
end
def edit
cust_id = params[:cust_id]
#cust_info = CustInfo.find(:all, :conditions => ["cust_id = ?", cust_id])
end
end
end
Change #cust_info = CustInfo.find(:all, :conditions => ["cust_id = ?", cust_id])
to
#cust_info = CustInfo.find(cust_id)

Resources