RSpec test of Rails model class method - ruby-on-rails

I have a method in my model
class Announcement < ActiveRecord::Base
def self.get_announcements
#announcements = Announcement.where("starts <= :start_date and ends >= :end_date and disabled = false",
{:start_date => "#{Date.today}", :end_date => "#{Date.today}"})
return #announcements
end
end
I am trying to write rspec for this method, as i am new to rspec cant proceed
describe ".get_announcements" do
before { #result = FactoryGirl.create(:announcement) }
it "return announcements" do
end
end
Please help

Solution for my question
describe ".get_announcements" do
before { #result = FactoryGirl.create(:announcement) }
it "return announcement" do
Announcement.get_announcements.should_not be_empty
end
end

describe ".get_announcements" do
let!(:announcements) { [FactoryGirl.create!(:announcement)] }
it "returns announcements" do
expect(Announcement.get_announcements).to eq announcements
end
end
Note the use of let! to immediately (not lazily) assign to announcements.
Does the class method really need to define an instance variable? If not, it could be refactored to:
class Announcement < ActiveRecord::Base
def self.get_announcements
Announcement.where("starts <= :start_date and ends >= :end_date and disabled = false",
{:start_date => "#{Date.today}", :end_date => "#{Date.today}"})
end
end

Related

Validations: When mapping ranges of dates, how to only map a range when card does not match another card

I'm setting up a validation, where a person can only add a price if that period does not have a price yet. So for everything works as expected and my form return a validation error when it is supposed to.
A problem arrises when I would like to check if there is a card (e.g. discount card). ==> as a period is allowed to overlap if the card does not match.
validator
class AccommodationPriceAvailabilityValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
accommodation_prices = AccommodationPrice.where("accommodation_category_id =?", record.accommodation_category_id)
accommodation_price = AccommodationPrice.where("id=?", record.id)
if accommodation_price.empty?
#below code works, without checking the card =>
date_ranges = accommodation_prices.map { |b| (b.start_date..b.end_date)}
#attempt below to include the card, but not working =>
date_ranges = accommodation_prices.where.not(:card => record.card).map { |b| (b.start_date..b.end_date)}
#rest of the code
date_ranges.each do |range|
if range.include? value
record.errors.add(attribute, "is overlapping with another period")
end
end
else
date_ranges = accommodation_prices.where.not('id=?', record.id).map { |b| b.start_date..b.end_date }
date_ranges.each do |range|
if range.include? value
record.errors.add(attribute, "is overlapping with another period")
end
end
end
end
end
If by "not working" you mean your query is not retrieving all therecords you want, it is because you are adding a condition to an Activerecord which is already filtered by another where statement.
class AccommodationPrice < ApplicationRecord
has_one :accommodation_category
scope :without_card, -> (card) { where().not(card: card) }
scope :all_except, -> (id) { where().not(id: id) }
def self.by_category(id)
where(accommodation_category_id: id)
end
def range
start_date..end_date
end
end
class AccommodationCategory < ApplicationRecord
belongs_to :accommodation_price
end
class AccommodationPriceAvailabilityValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
accommodation_prices = AccommodationPrice.by_category(record.accommodation_category_id)
accommodation_price = AccommodationPrice.find(record.id)
if accommodation_price
accomodations_filtered = accommodation_prices.all_except(record.id)
else
accomodations_filtered = AccommodationPrice.without_card(record.card)
end
to_accomodation_date_ranges(accomodations_filtered).each do |range|
if range.include? value
record.errors.add(attribute, "is overlapping with another period")
end
end
end
private
def to_accomodation_date_ranges(accomodation_prices)
accomodation_prices.map { |b| b.range }
end
end

Restrict the chance to delete a booking just more than x hours before departure in Rails

I want users not to be able to cancel a booking just 2 hours before departure time.
I don't know where can I write this restriction. Should I write it in the model or in the controller application?
This is the pseudo-code I wrote so far:
class CancelValidator < ActiveMOdel::Validator
def validate(record)
if record.date_trip.to_time < Date.now + 2
record.errors[:base] << 'error'
end
end
end
EDIT: This is all the code, but it still lets me destroy the booking.. why?
class CountValidator < ActiveModel::Validator
def validate(record)
if (record.second || record.first)
record.errors[:base]<< ' error '
end
end
end
class DepartureValidator < ActiveModel::Validator
def validate(record)
if record.date_trip.to_date < Date.today
record.errors[:base]<< ' error '
end
end
end
class Reservation < ActiveRecord::Base
validates_with DepartureValidator
validates_with CountValidator
before_destroy :ensure_deletable
belongs_to :dep ,:class_name => 'Stop', :foreign_key => 'dep_id'
belongs_to :arr ,:class_name => 'Stop',:foreign_key => 'arr_id'
belongs_to :route
belongs_to :user
delegate :CountStop, :to => :route, prefix: true, :allow_nil => false
delegate :city ,:to => :arr, :allow_nil => false
delegate :city ,:to => :dep, :allow_nil => false
def division
return Reservation.select{|r| r.route_id == route_id && r.date_trip == date_trip }
end
def second
if (class_point == 2)
y=division.select{ |l| l.class_point == 2 }.count
if(y+1 > route.train.second_class_seats)
return true
end
end
return false
end
def first
if (class_point == 1)
y=division.select{ |l| l.class_point == 1 }.count
if(y+1 > route.train.prima_classe_seats)
return true
end
end
return false
end
def ensure_deletable
self.date_trip.to_time < Time.now + 2
end
end
Since you delete the value, you're going to want to add a callback instead.
The benefit of this is that, before you go and delete the entity, you can decide to stop it outright if it fails your condition.
Here's an example below. Caution: this is untested, but this should give you the gist of things.
class Booking < ActiveRecord::Base
before_destroy :ensure_deletable
private
def ensure_deletable
self.date_trip.to_time < Date.now + 2
end
end
Remember, from the documentation:
The method reference callbacks work by specifying a protected or private method available in the object...

Call a Method Model inside Controller

I have the following model;
(app/models/student_inactivation_log.rb)
class StudentInactivationLog < ActiveRecord::Base
belongs_to :student
belongs_to :institution_user
belongs_to :period
validates_presence_of :student_id, :inactivated_on, :inactivation_reason
INACTIVATION_REASONS = [{ id: 1, short_name: "HTY", name: "You didn't study enough!"},
{ id: 2, short_name: "KS", name: "Graduated!"},
{ id: 3, short_name: "SBK",name: "Other Reason"}]
Class methods
class << self
def inactivation_reason_ids
INACTIVATION_REASONS.collect{|v| v[:id]}
end
def inactivation_reason_names
INACTIVATION_REASONS.collect{|v| v[:name]}
end
def inactivation_reason_name(id)
INACTIVATION_REASONS.select{|t| t[:id] == id}.first[:name]
end
def inactivation_reason_short_name(id)
INACTIVATION_REASONS.select{|t| t[:id] == id}.first[:short_name]
end
def inactivation_reason_id(name)
INACTIVATION_REASONS.select{|t| t[:name] == name}.first[:id]
end
end
# Instance methods
def inactivation_reason_name
self.class.inactivation_reason_name(self.inactivation_reason)
end
def inactivation_reason_short_name
self.class.inactivation_reason_short_name(self.inactivation_reason)
end
def inactivation_reason_id
self.class.inactivation_reason_id(self.inactivation_reason)
end
end
I would like to call these inactivation reasons from my controller, which is app/controllers/student/session_controllers.rb file:
class Student::SessionsController < ApplicationController
layout 'session'
def create
student = Student.authenticate(params[:student_number], params[:password])
if student.active
session[:student_id] = student.id
redirect_to student_main_path, :notice => 'Welcome!'
elsif (student and student.student_status == 3) or (student and !student.active)
flash.now.alert = "You can't login because #REASON_I_AM_TRYING_TO_CALL"
render 'new'
else
....
end
end
I would like to show students their inactivation reason on the systems if they can't login.
How can I call my INACTIVATION_REASONS from this controller file? Is it possible?
Thanks in advance!
That's just a constant, so you can call it as constant anywhere.
StudentInactivationLog::INACTIVATION_REASONS
Update
I realized actually what you want is to use a reason code or short name saved in db to represent the string.
If so, I recommend you to use the short name directly as Hash. "id" looks redundant for this light case.
INACTIVATION_REASONS = {"HTY"=>"You didn't study enough!",
"KS"=>"Graduated!",
"SBK"=>"Other Reason"}
validates :inactivation_reason, inclusion: { in: INACTIVATION_REASONS.keys,
message: "%{value} is not a valid short name" }
def full_reason_message
INACTIVATION_REASONS[self.inactivation_reason]
end
Then, to show full message of a reason in controller
reason = #student.full_reason_message
This is the idea. I havn't checked your other model codes. You'll need to save reason as the short name instead of id, and need to revise/remove some code if you decide to use it in this way.

How to return an unchanged AR relation from a class method

I have code like this:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return self
end
end
end
end
I need to call it like this:
Item.where("created_at > ?", Time.now - 1.week).for_category(#category)
#category may or may not be null. In the case where category is null, I want the method to simply pass through and return the relation unchanged. Of course, return self simply returns the Item class.
What would be the correct way to do this?
Are you trying to return the Scope (as opposed to the Class itself) for further scope action? If so, then something like the following should work:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return scoped
end
end
end
end
HTH
class Item < ActiveRecord::Base
def self.for_category(c)
conditions = {:category_id => c.id}
conditions.delete_if {|key,val| val.blank? }
self.where(conditions)
end
end
Your Item is associated with Category ? If yes then you can simply get all item categories by Item.where("created_at > ?", Time.now - 1.week).categroies not need for above code.
#scoped was deprecated in Rails 4. You can use #all to achieve the same effect:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return all
end
end
end
end

attr_accessor variable nil when doing before_validation callback

I am doing a before_validation as follows:
event.rb
attr_accessor :start_date
attr_accessible :start_time #recorded in database as a datetime
before_validation :build_start_time
...
def build_start_time
begin
self.start_time = DateTime.parse(start_date)
rescue
errors.add(:start_date, "invalid date")
return false
end
end
and the controller looks like:
def create
#event = events.build(params[:event])
if #event.save
# some other method calls
redirect_to #event
else
redirect_to :root
end
end
start_date is being set by a <%= f.text_field :start_date %> call in a form view, and when I check the params it is being passed to the 'Create' method of the model controller correctly, but in the build_start_time method it is nil, so self.start_time is not being set. Can you explain why it would be nil and what the solution would be? I also tried referring to it as self.start_date but that didn't make a difference.
Thanks
Have you tried making start_date also accessible?
Either you call attr_accessible with start_date so build() can actually set it, or you can change your controller to:
def create
#event = events.build(params[:event])
#event.start_date = params[:event][:start_date]
if #event.save
# some other method calls
redirect_to #event
else
redirect_to :root
end
end
tente assim.
#app/models/adm/video.rb
class Adm::Video < ActiveRecord::Base
validates :titulo, :url_codigo, presence: true
before_validation(on: [ :create, :update ]) do
self.url_codigo = parse_youtube(url_codigo) #url_codigo = params[:adm_video][:url_codigo]
end
private
# pega só o codigo do link youtube para inserir no banco
def parse_youtube(url)
if !url.blank?
regex = /(?:.be\/|\/watch\?v=|\/(?=p\/))([\w\/\-]+)/
return url.match(regex)[1] # https://www.youtube.com/watch?v=iX_rKHnKJSg = iX_rKHnKJSg
end
end
end
grava no banco de dados sò código do video = iX_rKHnKJSg = https://www.youtube.com/watch?v=iX_rKHnKJSg = iX_rKHnKJSg.
records in the database sò code iX_rKHnKJSg video = # = https://www.youtube.com/watch?v=iX_rKHnKJSg iX_rKHnKJSg

Resources