How to get value from select field in controller? - ruby-on-rails

I have a problem with get value from :days new form.
new.html.haml
= f.select :days, [['1 month', 30], ['2 months', 60], ['3 months', 90]]
controller
def create
#bought_detail = BoughtDetail.new(bought_detail_params)
#bought_detail.entry_type = #entry_type
#bought_detail.person = current_person
if #bought_detail.entry_type.kind == 'Karnet'
#bought_detail.cost = #bought_detail.entry_type.price * (days / 30).floor
else
#bought_detail.cost = #bought_detail.entry_type.price
end
end
private
def bought_detail_params
params.require(:bought_detail).permit(:bought_data, :start_on, :end_on, :entry_type_id, :days, :person_id, :credit_card, :card_code)
end
Model
belongs_to :entry_type
belongs_to :person
before_save :set_bought_data, :set_end_on, :set_start_on
attr_accessor :credit_card, :card_code, :days
def set_bought_data
self.bought_data = Date.today
end
def set_start_on
self.start_on = bought_data if start_on.nil?
end
def set_end_on
days = "7" if days.nil?
self.end_on = Date.today + days.to_i
end
When I fill new form and click submit I get this error:
undefined local variable or method `days'
I want to deliver value from select field e.g. when I choose 2 months, value of the day will be 60 and I will can get calculate #bought_detail.cost. How to do this?
Thanks in advance!

In controller you're trying to access undefined variable days, change to accessing the model:
#bought_detail.cost = #bought_detail.entry_type.price * (#bought_detail.days.last / 30).floor
In model change to:
self.days = "7" if days.nil?
because otherwise you'll be assigning to local variable, not the attribute.

In your controller's create method. Where are you getting the days from?
#bought_detail.cost = #bought_detail.entry_type.price * (days.last / 30).floor
This bit specifically: days.last / 30. Ruby can't find days method or local variable. I guess you want to access it from params[:your_model][:days]?

Related

How To Fix undefined method `product' for #<LineItem::ActiveRecord_Relation:0x0000000017b22f70>

I'm try product quantity - 1 but ı get this error
line_item.rb
belongs_to :order
belongs_to :product
payment.rb
has_many :orders
undefined method `product' for # LineItem::ActiveRecord_Relation:0x0000000017b22f70>
#line_item = LineItem.where(:order_id => params[:zc_orderid])
#line_item.product.quantity = #line_item.product.quantity - 1
if #line_item.product.quantity == 0
#line_item.product.sold = true
end
#line_item.product.save
If you use where, you don't get a single LineItem object, but a LineItem::ActiveRecord_Relation object. If that condition is enough to get just one record then use find_by. If it's not you need to think more about the logic because you'd get more than one object.
#line_item = LineItem.find_by(:order_id => params[:zc_orderid])
If you want to decrease the quantity of all those line items I'd do something like
LineItem.transaction do
LineItem.where(:order_id => params[:zc_orderid]).each do |line_item|
line_item.product.quantity = line_item.product.quantity - 1
if line_item.product.quantity == 0
line_item.product.sold = true
end
line_item.product.save
end
end
Since Order has many LineItem you should expect more than one line, so should rewrite your code:
LineItem.where(:order_id => params[:zc_orderid]).each do |line_item|
product = line_item.product
product.quantity -= 1
if product.quantity == 0
product.sold = true
end
product.save
end
Btw, consider add a Transaction.
LineItem.where(:order_id => params[:zc_orderid]) its return as array format.
So you can fetch by following
LineItem.find_by(order_id: params[:zc_orderid]). its return single active record

Create a record to the database without a form in Ruby on Rails 5

In my controller I have defined a method that I want to save to my database automatically without a form.
This is what I have so far, but nothing is being saved to the database.
Here's the method
def recommended_recipes
#today = Date.today
#recRecipe = RecommendedRecipe.where(user_id: current_user, day: #today)
if #recRecipe.blank?
response = RestClient.get("https://spoonacular-recipe-food-nutrition-v1.p.mashape.com/recipes/mealplans/generate?targetCalories=3000&timeFrame=day", headers={"X-Mashape-Key" => "",
"Accept" => "application/json"})
#parsedResponse = JSON.parse(response)
#recRecipes = #parsedResponse['meals']
#recRecipesNutrients = #parsedResponse['nutrients']
#totalCalories = #recRecipesNutrients['calories']
#totalProteins = #recRecipesNutrients['protein']
#totalFat = #recRecipesNutrients['fat']
#totalCarbohydrates = #recRecipesNutrients['carbohydrates']
#newRecRecipe = RecommendedRecipe.create(meals_response: #recRecipes, total_calories: #totalCalories, total_proteins: #totalProteins, total_fat: #totalFat, total_carbohydrates: #totalCarbohydrates, day: #today, user_id: current_user)
end
end
I want to save the #newRecipe to my database called recommended_recipes whenever the method is called.
How can I make a record in the database?
Thanks in advance!
After hours of kicking myself in the head I did this in the model:
class RecommendedRecipe < ApplicationRecord
belongs_to :user, optional: true
end
I added the optional: true

Rails: Undefined method in model

I'd like to convert a unix time to human time before saving my object from an api.
But I cannot access to my method format date, it raise me :
undefined method `format_date' for 1467738900000:Fixnum
My model :
class Conference < ActiveRecord::Base
validates_presence_of :title, :date
validates :date, :uniqueness => true
def self.save_conference_from_api
data = self.new.data_from_api
self.new.parisrb_conferences(data).each do |line|
conference = self.new
conference.title = line['name']
conference.date = line['time'].format_date
conference.url = line['link']
if conference.valid?
conference.save
end
end
self.all
end
def format_date
DateTime.strptime(self.to_s,'%Q')
end
line['time'] is not an instance of your Conference class, so you can't call format_date method on it. Instead, for example, you can make format_date a class method:
def self.format_date str
DateTime.strptime(str.to_s,'%Q')
end
And then call it like this:
conference.date = format_date(line['time'])
The other option is to use a before_validation callback (attribute assignment will be as follows: conference.date = line['time'] and there is no need for format_date method):
before_validation -> r { r.date = DateTime.strptime(r.date.to_s,'%Q') }
You are getting the date in unix time milliseconds. You can do like this
conference.date = DateTime.strptime(line['time'].to_s,'%Q')

Between SQL ActiveRecord

I recived Task:
Add a method to the Profile class, called get all profiles, which:
• accepts a min and max for the birth year
• issues a BETWEEN SQL clause in a where clause to locate Profiles with birth years that are between min
year and max year
• defends itself against SQL injection when applying the parameters to the SQL clauses
• returns a collection of Profiles in ASC birth year order
Profile Class:
class Profile < ActiveRecord::Base
belongs_to :user
validates :first_name, presence: true
validates :last_name, presence: true
validates :gender, inclusion: %w(male female)
validate :first_and_last
validate :male_Sue
def first_and_last
if (first_name.nil? and last_name.nil?)
errors.add(:base, "Specify a first or a last.")
end
end
def male_Sue
if (first_name == "Sue" and gender == "male")
errors.add(:base, "we are prevent male by name Sue.")
end
end
def get_all_profiles
end
end
How can complete this task? explanation appriciating...
I should pass this rspec test:
context "rq14" do
context "Profile has a get_all_profiles method" do
subject(:profile) { Profile.new }
it { is_expected.to respond_to(:get_all_profiles) }
end
it "will return a list of profiles between requested birth years in ascending order" do
user = User.create(:username=>"testUser", :password_digest=>"xxxx")
startYear = 1960
endYear = 2000
testYear = 1985
testCount = 0
(0..20).each do |i|
birthYear = startYear + rand(0..(endYear - startYear))
if (birthYear <= testYear)
testCount = testCount + 1
end
profile = Profile.create(:user_id=>user.id, :gender=>"male", :birth_year=>birthYear, :first_name=>"User #{i}", :last_name=>"Smith#{i}")
end
profileGroup = Profile.new.get_all_profiles(startYear, testYear)
expect(profileGroup.length).to be(testCount)
# test that results are sorted by birthyear and are ascending
year = startYear
profileGroup.each do |t|
expect(t.birth_year).to be >= year
year = t.birth_year
end
end
end
end
Thanks, Michael.
It's the answer:
def get_all_profiles(start_year, end_year)
Profile.where(:birth_year => start_year..end_year).order(:birth_year )
end
It's more Rails style to use a scope:
scope :all_profiles -> (date_from, date_to) { where birth_date: date_from..date_to }

Including validation method in another method

I have a validation method that has to verify values assigned in another method, how can i get it to recognise those values before validation? the pay_must_be_same_to_amount method needs some values from the create_items_from_readings method
class Invoice < ActiveRecord::Base
attr_accessible :approved_by, :due_date, :invoice_date, :reading_ids, :terms, :customer_id, :customer, :status, :reference_no, :payment_method, :amount, :payment_date
has_many :invoice_items, :dependent => :destroy
belongs_to :customer, :inverse_of => :invoices
validate :pay_must_be_same_to_amount
def create_item_from_readings
item = invoice_items.new
item.rate = customer.unit_cost
readings_in_this_period = customer.unbilled_readings.where('date_of_reading <= ?', invoice_date).order(:date_of_reading)
return nil if readings_in_this_period.empty?
self.reading_ids = readings_in_this_period.collect(&:id).join(',')
total_units = 0
readings_in_this_period.each do |reading|
total_units = total_units + reading.units_used1 + reading.units_used2 + reading.units_used3
end
item.amount = total_units * customer.unit_cost * customer.ct_ratio
item.tax_amount = (item.amount * Settings.vat) if customer.pays_vat
invoice_from_reading = readings_in_this_period.first.previous_reading
invoice_from_reading ||= readings_in_this_period.first
invoice_to_reading = readings_in_this_period.last
#Select Item description based on Phase type
if customer.phase_type == 'Single Phase'
item.description = "Electricity used from #{invoice_from_reading.date_of_reading.strftime('%d/%m/%Y')} with readings #{invoice_from_reading.reading1} to #{invoice_to_reading.date_of_reading.strftime('%d/%m/%Y')} with reading #{invoice_to_reading.reading1} - #{total_units.to_i} total units"
else
item.description = "Electricity used from #{invoice_from_reading.date_of_reading.strftime('%d/%m/%Y')} with readings, R1: #{invoice_from_reading.reading1}, R2: #{invoice_from_reading.reading2}, R3: #{invoice_from_reading.reading3} to #{invoice_to_reading.date_of_reading.strftime('%d/%m/%Y')} with readings, R1: #{invoice_to_reading.reading1}, R2:#{invoice_to_reading.reading2}, R3: # {invoice_to_reading.reading3}- #{total_units.to_i} total units"
end
end
end
and the validation method is below, it needs to compare the item.amount above to the amount in the class Invoice
def pay_must_be_same_to_amount
if item.amount < self.amount && item.amount != self.amount
self.errors.add :amount, 'The payment amount should be equal to amount on invoice'
end
end
end
A few comments: create_item_from_readings is way too complicated. I can't tell what it's supposed to do, but if you run it, I believe it will return a string (one of the two from the last if statement).
If all you need to do is compare item.amount to the invoice amount attribute, that's simple. You can use your validation method almost as you've written it, plus a few other methods as needed.
def item_amount
total_units * customer.unit_cost * customer.ct_ratio
end
def total_units
readings_in_this_period.inject(0) {|total,r| total + r.units_used1 + r.units_used2 + r.units_used3 }
end
def pay_must_be_same_to_amount
if item_amount != amount
errors.add :amount, 'The payment amount should be equal to amount on invoice'
end
end
The code for both of those supplementary methods is simply modified code from your longer method.
A good rule of practice is that if a method is longer than one line, and you can't tell what it's for by glancing at it, it's too long (this isn't always true, but it's worth considering for complicated methods).
The solution to the question is
def pay_must_be_same_to_amount
sum = 0
self.invoice_items.each do |invoice_item|
sum = sum + invoice_item.amount
end
if sum != self.amount
self.errors.add :amount, 'The payment amount should be equal to amount on invoice'
end
end

Resources