Rails 4 : Access attributes from model - ruby-on-rails

I have a Model Reservation with 2 columns, user_id and teleporter_id.
I want to lock the creation of a Reservation to 3 same teleporter_id but I don't know how to access to the attribute teleporter_id from the Model that I'm creation.
Here my code :
class Reservation < ActiveRecord::Base
# Relations
belongs_to :teleporter
belongs_to :user
# Validations
validate :max_reservation
def max_reservation
if Reservation.where(:teleporter_id => self.teleporter_id).count >= 3
errors.add(:base, t('reservation.model.error.max_reservation'))
end
end
end
I think that the problem is from self.teleporter_id but I don't know how access to the attribut teleporter_id from the current model.

Try this:
def max_reservation
_id = self.teleporter_id
errors.add(:base, t('reservation.model.error.max_reservation')) unless Reservation.where(teleporter_id: _id).count <= 3
end

Related

Pass parameter to callback in Rails

I have 2 models: User and Favorite. In model Favorite:
class Favorite < ApplicationRecord
belongs_to :user, foreign_key: :user_id
def self.add_favorite(options)
create!(options)
end
def self.unfavorite(options)
where(options).delete_all
end
Now, I want to limit number of records saved to Favorite is 10. It mean that users are only liked 10 products. I researched google, someone said that I try to use callback and I think it's right way, but it raise 2 questions:
1. Can I use query in method for callback?
2. Callback can be pass argument?
It is sample code I think:
class Favorite < ApplicationRecord
after_create :limit_records(user_id)
belongs_to :user, foreign_key: :user_id
def self.add_favorite(options)
create!(options)
end
def self.unfavorite(options)
where(options).delete_all
end
def limit_records(user_id)
count = self.where(user_id: user_id).count
self.where(used_id: user_id).last.delete if count > 10
end
If user had 10 favorite, when they like any products, callback will be called after Favorite is created and will be delete if it's 11th record.
You have:
belongs_to :user, foreign_key: :user_id
in your Favorite model and limit_records is an instance method on Favorite. So you have access to the user as self.user_id (or just user_id since self is implied) inside limit_records and there is no need for an argument:
after_create :limit_records
def limit_records
# same as what you have now, `user_id` will be `self.user_id`
# now that there is no `user_id` argument...
count = self.where(user_id: user_id).count
self.where(used_id: user_id).last.delete if count > 10
end

Rails Where vs Join vs Union

Having trouble with my activerecord searches. I thought I had my models setup correctly, but I’m poor with my joins (not sure if a join or union is the correct way to go? It shouldn’t be this difficult).
I have guides creating bids on trips that have start_dates. I want to create a list of bids that have expired (ie. the start date is in the past). Guides can also have LosingBids if a bid has been declined
In a perfect world I would have one resultset that includes both losing bids and expired bids for that guide, but I’m find with 2 different result sets. Unfortunately I can’t get any of the “expired bids” to work. Results/errors in the comments of the code.
class GuidesController < ApplicationController
def expired_declined
#this declined_bids call works
#declined_bids = LosingBid.where("guide_id = ?", current_guide.id.to_s)
#this expired_bids call returns Trips, not Bids
#expired_bids = Bid.where("guide_id = ?", current_guide.id.to_s).expired
#this expired_bids call gives me the following error:
#SQLite3::SQLException: no such column: trips.start_date: SELECT 1 AS one FROM #”bids" WHERE (guide_id = '1') AND (trips.start_date < '2018-05-30') LIMIT ?
#expired_bids = Bid.where("guide_id = ?", current_guide.id.to_s).where("trips.start_date < ?", Date.today)
end
end
class Guide < ApplicationRecord
has_many :bids
has_many :losing_bids
end
class Trip < ApplicationRecord
has_many :bids
end
class Bid < ApplicationRecord
belongs_to :trip
belongs_to :guide
def self.expired
Trip.where("start_date <= ?", Date.today) #.where("guide_id = ?", current_guide.id.to_s)
end
end
class LosingBid < ApplicationRecord
belongs_to :trip
belongs_to :guide
end
Trip.where("start_date <= ?", Date.today).bids will return you the expired bids.
You should move the expired scope in the Trip, rather than on the Bid.
If you want a scope on Bid you can define.
class Bid
scope :expired, -> { joins(:trip).where('trips.start_date <= ?', Date.current) }
end
I would really question if you need to have a separate LosingBid model or if its just creating duplication and unnecessary complexity. Instead just add an enum column to bids which contains the status:
class Bid
enum status: [:pending, :declined, :expired, :accepted]
end
This is just a simple integer column that acts as a bit mask.
This will simply let you query by:
Bid.pending
Bid.expired
Bid.where(status: [:pending, :accepted])
Bid.where.not(status: :accepted)
You can simply reject a bid by:
class BidsController
# PATCH /bids/decline
def reject
#bid.declined!
redirect_to bid, notice: 'Bid declined'
end
end
You could then setup scheduled task which runs once per day to automatically expire tasks (example with the whenever gem):
every 1.days do
runner "BidExpiryService.perform"
end
# app/services/bid_expiry_service.rb
module BidExpiryService
def self.perform
bids = Bid.pending
.joins(:trip)
.where('trips.start_date <= ?', Date.current)
bids.update_all(status: Bid.statuses[:expired])
# #todo notify guides that bid has expired
end
end

rails validate dependent model

There have 2 tables: Orders and Arrivals. There can be many arrivals on an order. I want to validate the creation of arrivals for a specific order.
Orders has fields book_id and quantity:integer
Arrivals has fields order:belongs_to and quantity:integer
Order.rb:
class Order < ActiveRecord::Base
has_many :arrivals
def total_arrival_quantity
arrivals.map(&:quantity).sum
end
def order_quantity_minus_arrival_quantity
quantity - total_arrival_quantity
end
end
Arrival.rb:
class Arrival < ActiveRecord::Base
belongs_to :order
validates :total_arrival_quantity_less_or_equal_to_order_quantity, on: create
validates :current_arrival_quantity_less_or_equal_to_order_quantity, on: create
def current_arrival_quantity_less_or_equal_to_order_quantity
self.quantity <= order.quantity
end
end
How can I make the two validations work?
Something like this should work,
validate :order_quantity, on: :create
private
def order_quantity
if quantity > order.order_quantity_minus_arrival_quantity
errors.add(:quantity, 'cannot be greater than ordered quantity.')
end
end

Rails Methods that call themselves

In Rails is there a way that I can make a method call itself based on a change in the database? For instance, lets say I have two classes: Products and Orders.
Orders have three possible enum values:
class Order < ActiveRecord::Base
enum status: [:pending, :processing,:shipped]
belongs_to :products
end
I would like to batch process Orders so when a product has 50 orders, I want it to set all Orders associated with it to processed. Orders default to :pending. To change an order to :processing I would call order.processing!. I could write a method into the Products model like:
def process_orders
if self.orders.count=50
self.orders.each do |order|
order.processing!
end
end
The problem with this is that I would have to call the process_orders method for it to execute, is there anyway I could make it automatically execute once a product has 50 orders?
This is sounds like a good opportunity to use an Active Record Callback.
class Order < ActiveRecord::Base
belongs_to :product
after_save do
product.process_orders if product.pending_threshold_met?
end
end
class Product < ActiveRecord::Base
has_many :orders
def pending_threshold_met?
orders.where(status: :pending).count >= 50
end
end
I think you can use update_all to update the status column of all of your orders at once rather looping through them one by one:
self.orders.update_all(status: :processing)
and wrap that inside a callback.
Something like this:
class Order < ActiveRecord::Base
after_save do
product.process_orders if product.has_fifty_pending_orders?
end
# rest of your model code
end
class Product < ActiveRecord::Base
# rest of your model code
def process_orders
self.orders.update_all(status: :processing)
end
def has_fifty_pending_orders?
self.orders.where(status: :pending).count >= 50
end
end

Validate the quantity of associated objects

Given this:
class User < ActiveRecord::Base
has_many :photos
MAX_PHOTOS = 5
validate :quantity_of_photos
def quantity_of_photos
???
end
end
And this:
#user.photos.size # => 5
I need this to fail:
#user.photos << Photo.create(valid_photo_attributes)
How do I do this validation?
Move the quantity of photos method to the Photo model:
class Photo < ActiveRecord::Base
belongs_to :user
validates :quantity_of_photos
def quantity_of_photos
if new_record? && user.photos.size >= User::MAX_PHOTOS
errors.add_to_base "You cannot have more than #{MAX_PHOTOS} photos."
end
end
end
The validity of a ActiveRecord instance is determined by whether there are errors in it's errors array.

Resources