How to update associated data on load in Rails 3.x?
When I load a stock symbol, I would like to update its quotes if they are outdated. Since ActiveRecord does not have a callback like after_load, what is the right way to do this?
My pseudo code looks like
class Stock < ActiveRecord::Base
has_many :quotes, :limit => 15, :order => 'date desc'
attr_accessible :symbol
after_load :update_history
#correct code is ( Thanks Dutow)
after_initialize :update_history
def update_history
if (quotes.count > 0)
today = Time.now
get_and_store_history unless (quotes[0].updated_at >= today.beginning_of_day && quotes[0].updated_at <= today.end_of_day)
end
end
def get_and_store_history
#Update quotes
end
end
ActiveRecord has a method called after_initialize, which is a callback like after_load.
Related
What is the best way to fetch latest event in the past and soonest event in the future?
Client.rb:
has_many :events
Event.rb:
belongs_to :client
validates :starts_at, presence: :true
Clients_controller.rb:
#soonest_event = #client.events.where("starts_at >= ?", Time.zone.now).order("starts_at ASC").first.starts_at
#latest_event = #client.events.where("starts_at <= ?", Time.zone.now).order("starts_at DESC").first.starts_at
Client/show:
Soonest event in the future:
= distance_of_time_in_words(Time.now, #soonest_event)
Latest event in the past:
= distance_of_time_in_words(Time.now, #latest_event)
If a client does not have a past event or a future event, it shows an error in the controller like:
undefined method `starts_at' for nil:NilClass
1) How can I make the code more efficient?
2) How can I tackle the error?
I solved it with:
Client.rb:
def next_event
events.where("starts_at >= ?", Time.zone.now).order("starts_at ASC").pluck(:starts_at).first
end
def last_event
events.where("starts_at <= ?", Time.zone.now).order("starts_at DESC").pluck(:starts_at).first
end
Client/show:
- if #client.last_event.present?
= distance_of_time_in_words(Time.now, #client.last_event)
- if #client.next_event.present?
= distance_of_time_in_words(Time.now, #client.next_event)
I have many instances in my application where I use single table inheritance and everything works fine in my development environment. But when I release to production (using passenger) I get the following error:
undefined method `before_save' for InventoryOrder:Class
(NoMethodError)
Why would this work in my dev environment and not work in production? Both are using Rails 4.2 and Ruby 2.1.5. Could this be a problem with passenger?
Here is the InventoryOrder class:
class InventoryOrder < Order
def self.model_name
Order.model_name
end
before_save :ensure_only_feed_types
def ensure_only_feed_types
order_products.each do |op|
if !ProductTypes::is_mix?(op.product_type.type)
raise Exceptions::FailedValidations, _("Can't have an inventory order for anything but mixes")
end
end
end
def self.check_if_replenishment_order_is_needed(product_type_id)
prod_type = ProductType.find(product_type_id)
return if prod_type.nil? || prod_type.min_system_should_have_on_hand.nil? || prod_type.min_system_should_have_on_hand == 0
amount_free = Inventory::inventory_free_for_type(product_type_id)
if prod_type.min_system_should_have_on_hand > amount_free
if prod_type.is_mix?
InventoryOrder::create_replenishment_order(product_type_id, prod_type.min_system_should_have_on_hand - amount_free)
else
OrderMoreNotification.create({subject: "Running low on #{prod_type.name}", body: "Should have #{prod_type.min_system_should_have_on_hand} of unreserved #{prod_type.name} but only #{amount_free} is left"})
end
end
end
def self.create_replenishment_order(product_type_id, amount)
# first check for current inventory orders
orders = InventoryOrder.joins(:order_products).where("order_products.product_type_id = ? and status <> ? and status <> ?", product_type_id, OrderStatuses::ready[:id], OrderStatuses::completed[:id])
amount_in_current_orders = orders.map {|o| o.order_products.map {|op| op.amount }.sum }.sum
amount_left_to_add = amount - amount_in_current_orders
if amount_left_to_add > 0
InventoryOrder.create({pickup_time: 3.days.from_now, location_id: Location::get_default_location.id, order_products: [OrderProduct.new({product_type_id: product_type_id, amount: amount_left_to_add})]})
end
end
def self.create_order_from_cancelled_order_product(order_product)
InventoryOrder.create({
pickup_time: DateTime.now.change({ min: 0, sec: 0 }) + 1.days,
location_id: Location::get_default_location.id,
order_products: [OrderProduct.new({
product_type_id: order_product.product_type_id,
feed_mill_job_id: order_product.feed_mill_job_id,
ration_id: order_product.ration_id,
amount: order_product.amount
})],
description: "Client Order for #{order_product.amount}kg of #{order_product.product_type.name} was cancelled after the feed mill job started."
})
end
end
And here is it's parent class:
class Order < ActiveRecord::Base
#active record concerns
include OrderProcessingInfo
belongs_to :client
belongs_to :location
has_many :order_products
before_destroy :clear_order_products
after_save :after_order_saved
before_save :on_before_save
accepts_nested_attributes_for :order_products, allow_destroy: true
after_initialize :init #used to set default values
validate :client_order_validations
def client_order_validations
if self.type == OrderTypes::client[:id] && self.client_id.nil?
errors.add(:client_id, _("choose a client"))
end
end
...
end
Thanks,
Eric
After doing some more digging and with the help of Roman's comment I was able to figure out that this issue was a result of me using an older convention for ActiveRecord::Concerns that works fine on windows but not on unix based systems.
According to this RailsCasts you can define your concerns like this:
In ../models/concerns/order/order_processing_info.rb
class Order
module OrderProcessingInfo
extend ActiveSupport::Concern
included do
end
...
end
But according to this the right way to define the concern would be to
1) Put it in ../models/concerns/[FILENAMEHERE] instead of ../models/concerns/[CLASSNAMEHERE]/[FILENAMEHERE]
2) Define the module without wrapping it in the class like this:
module OrderProcessingInfo
extend ActiveSupport::Concern
included do
end
end
Took some digging to get to the bottom of it but hopefully this might help someone else out there.
Does anyone know why the following will not work. Is it my code? I'm trying to call the assign_picks at specific times but I could not get it to work. I then tried the below (in 5 minutes time).
I've been following the instructions in this stackoverflow post
I rolled back the db and then migrated again but nothing happens after 5 minutes. I've tested the assign_picks method by calling it on a button press and it works fine.
Any ideas? I'v been stuck here for hours.
Thanks for reading.
Edit:
I meant to mention that I'm using a virtual machine on Nitrious.io. Would that have anything to do with it?
Models/match.rb
class Match < ActiveRecord::Base
attr_accessible :block, :round, :date, :day, :time, :venue, :team1, :team2, :played, :result, :resultString
has_many :match_picks
has_many :current_blocks
after_create :set_expiry_timer
# register the timer
def set_expiry_timer
delay(:run_at => 5.minutes.from_now).assign_picks
end
def assign_picks
#current_blocks = CurrentBlock.where(:id => 1)
#users = User.where(:curr_block != #current_blocks[0].block)
#matches = Match.where(:block => #current_blocks[0].block)
#users.each do |user|
#matches.each do |match|
MatchPick.create!(:userID => user.id, :matchID => match.id, :blockID => #current_blocks[0].block, :userPick => (0..2).to_a.sample)
end
end
# Increase the current block for application
CurrentBlock.where(:id => 1).each do |cb|
cb.update_attribute(:block, cb.block + 1)
end
end
end
For example in my Car model i have such fields:
color, price, year
and in form partial i generate form with all this fields. But how to code such logic:
user could enter color and year and i must find with this conditions, user could enter just year or all fields in same time...
And how to write where condition? I could write something like:
if params[:color].present?
car = Car.where(color: params[:color])
end
if params[:color].present? && params[:year].present?
car = Car.where(color: params[:color], year: params[:year])
end
and so over....
But this is very ugly solution, i'm new to rails, and want to know: how is better to solve my problem?
Check out the has_scope gem: https://github.com/plataformatec/has_scope
It really simplifies a lot of this:
class Graduation < ActiveRecord::Base
scope :featured, -> { where(:featured => true) }
scope :by_degree, -> degree { where(:degree => degree) }
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
end
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
has_scope :by_period, :using => [:started_at, :ended_at], :type => :hash
def index
#graduations = apply_scopes(Graduation).all
end
end
Thats it from the controller side
I would turn those into scopes on your Car model:
scope :by_color, lambda { |color| where(:color => color)}
scope :by_year, lambda { |year| where(:year => year)}
and in your controller you would just conditionally chain them like this:
def index
#cars = Car.all
#cars = #cars.by_color(params[:color]) if params[:color].present?
#cars = #cars.by_year(params[:year]) if params[:year].present?
end
user_params = [:color, :year, :price]
cars = self
user_params.each do |p|
cars = cars.where(p: params[p]) if params[p].present?
end
The typical (naive, but simple) way I would do this is with a generic search method in my model, eg.
class Car < ActiveRecord::Base
# Just pass params directly in
def self.search(params)
# By default we return all cars
cars = all
if params[:color].present?
cars = cars.where(color: params[:color])
end
if params[:price1].present? && params[:price2].present?
cars = cars.where('price between ? and ?', params[:price1], params[:price2])
end
# insert more fields here
cars
end
end
You can easily keep chaining wheres onto the query like this, and Rails will just AND them all together in the SQL. Then you can just call it with Car.search(params).
I think you could use params.permit
my_where_params = params.permit(:color, :price, :year).select {|k,v| v.present?}
car = Car.where(my_where_params)
EDIT: I think this only works in rails 4, not sure what version you're using.
EDIT #2 excerpt from site I linked to:
Using permit won't mind if the permitted attribute is missing
params = ActionController::Parameters.new(username: "john", password: "secret")
params.permit(:username, :password, :foobar)
# => { "username"=>"john", "password"=>"secret"}
as you can see, foobar isn't inside the new hash.
EDIT #3 added select block to where_params as it was pointed out in the comments that empty form fields would trigger an empty element to be created in the params hash.
I have a record called Feeds that contains the field 'last_visited' and 'last_modified', both are timestamps.
I'm trying to render a list in a view of alls Feeds where last_modified > last_visited.
I currently have this:
Controller
#feeds = #user.feeds
#feeds_hot = #feeds.where(['#feeds.last_modified > ?', #feeds.last_visited])
Have a feeling I'm way off track here. Should I also being using a model class to do this?
Any helps is greatly appreciated.
Edit:
Here's my model
class Feed < ActiveRecord::Base
attr_accessible :feed_id, :feed_url, :last_modified, :title, :url, :last_visited, :user_id
belongs_to :user
scope :hottest, lambda {where('last_modified > ?', :last_visited)}
def fetch_feed!
feed = Feedzirra::Feed.fetch_and_parse(feed_url) # probably want some eror handling here
self.title = feed.title
self.url = feed.url
self.last_modified = feed.last_modified
self.last_visited = feed.last_modified
self #or nil if you like
end
def self.check_for_update(feed)
fetched_feed = Feedzirra::Feed.fetch_and_parse(feed.feed_url)
entry = fetched_feed.entries.first
feed.last_modified = entry.published
end
def update_visit_date!
date = Time.now
update_attribute(:last_visited, date)
self
end
end
Edit
Updated code
Controller
def home
#user = current_user
#feeds = #user.feeds
#feeds_hot = #feeds.hottest
end
Model
attr_accessible :feed_id, :feed_url, :last_modified, :title, :url, :last_visited, :user_id
belongs_to :user
scope :hottest, lambda {where("'last_modified' > ?", 'last_visited')}
View
%ol.feeds.feeds_hot
- #feeds_hot.each do |feed|
%li.feed.hot[feed]
= render :partial => 'feeds/feed_link', :locals => {:feed => feed}
Unfortunately it's still not rendering the hot feeds in the view when I have a feed with the following data:
Last Modified 2013-06-14 23:49:07 UTC
Last Visited 2013-06-14 23:47:55 UTC
Last Modified is a few hours > than Last Visited
If you're looking to get a list of all feeds that have been modified more recently than the most recent visit on any feed, the following will work:
last_visit = Feed.order("last_visited").last.last_visited
#feeds = #user.feeds
#hot_feeds = Feed.where("last_modified > ?", last_visited)
EDIT:
Based on your comments and a re-reading of your question, the code posted above will not accomplish what you're trying to do. Since you're trying to get a list of all invites where each has been modified since it was last visited, you'll want to create a model scope to do the lookup with ActiveRecord. The following code should work:
# app/models/feed.rb
class Feed < ActiveRecord::Base
scope :hottest, lambda {where('last_modified > ?', :last_visited)}
end
Then, you can run the following in a console, controller, or view:
#user.invites.hottest
#=> array of all the user's invites that have been modified more recently than they have been viewed