Given three models, e.g, house, wall and door (a house has may walls and a wall has many doors):
House should have a counter cache column for all doors of all associated walls, since that's a fairly expensive query to make.
In order to update this column, I'm using after_create and after_destroy callbacks within the door model, which trigger the following methods successfully:
def increase_house_doors_count
House.increment_counter(:doors_count, house.id)
end
def decrease_house_doors_count
House.decrement_counter(:doors_count, house.id)
end
"house" is a method:
def house
wall.house
end
Initially I had used a slightly different but (IMO) more simple version:
def increase_house_doors_count
house.increment(:doors_count)
end
def decrease_house_doors_count
house.decrement(:doors_count)
end
But this latter version didn't update the counter when used within the model. Running the code directly from the console was successful, though.
What am i missing here?
Cheers!
Maybe try it like this:
house.increment!(:doors_count)
Perhaps it needs to be done in place.
Related
Hello, I am building a Ruby on Rails cashflow app where the "balance" field in the "accounts" table will be updated based on the "amount" field in the "incomes" table.
How should I deal with Income object updates (when "amount" is changed), so that the "balance" field in "accounts" is updated properly (first decreased by the previous "amount" of the "Income" and then updated with new "amount")?
Is it a good practice to use callbacks in the "Income" model and ActiveModel::Dirty methods such as "income.amount_was" to get the previous value?
I recommend to create a service object, instead of directly updating the Income#amount via income.update in the controller. Updating another model inside some other model's callback IMO increases coupling and debugging complexity as the responsibility bleeds outside its own.
For example, create a IncomeUpdaterService that does this:
class IncomeUpdaterService
def initialize(income)
#income = income
#account = ... # Depends on how they are connected
end
def update(params)
# DB Transaction
# You might want to lock the Account record
...
update_balance(params.amount) if params.amount
...
# DB Transaction Commit
end
private
def update_balance(new_amount)
prev_amount = #income.amount
balance_adjustment = new_amount - prev_amount
#account.balance += balance_adjustment
# I think theres a #account.increment(:balance, balance_adjustment)
# to avoid race condition instead of locking the record, but not sure
# if it's a good practice to use. From what I remember there's some
# kind of warning for using it.
#account.save
end
end
(For the self projecting master developers out there. Just wrote this quickly just to put it out there, don't be too hypercritical on this code as this is just an example and start insulting me, but feel free to suggest some improvements.)
The Review class you see below represent a review that a user submitted for a product. Somewhere else in the code, Review.recent is called with a product_id, which is just a unique number that represents a single product. Fill in the code to make it work as expected!
Review.recent - This function should return the 5 most recent reviews (sorted by submit_time) with the specified product_id.
<=> - This special Ruby function is called when comparing two objects for sorting. It returns 1, 0, or +1 depending on whether the object is less than, equal to, or greater than the other object. You'll want to sort items by submit_time so recent items appear first.
set_submit_time - This function is called right before a review is created. We can use Ruby's Time class to set submit_time to now so we know when the review was created.
I'm new to ruby and I want this code for my very important work so how can I complete it help me please!
class Review < ApplicationRecord
# Every Review has a product_id and submit time
attr_accessor :product_id
attr_accessor :submit_time
# Before the new record is created, we'll call the :set_submit_time method
before_create :set_submit_time
def self.recent(product_id)
# Return only the 5 newest results for this product
# Reference: https://ruby-doc.org/core-2.4.2/Enumerable.html
Review.all
end
def <=>(other_review)
# Implement the comparison function for sorting
# Reference: http://ruby-doc.org/core-2.4.2/Comparable.html
end
private
def set_submit_time
# Set the submit_time
# Reference: https://ruby-doc.org/core-2.2.4/Time.html
end
end
self.recent
This is asking you to order by submit_time and return the first 5 results.
To perform the ordering, see: https://apidock.com/rails/ActiveRecord/QueryMethods/order
To perform the limit, see: https://apidock.com/rails/ActiveRecord/QueryMethods/limit
If you're still stuck on this problem, please show us what you've tried.
<=>
If you click the link in the comment you provided (http://ruby-doc.org/core-2.4.2/Comparable.html), the solution is almost identical to that example.
If you're still stuck on this problem, please show us what you've tried.
set_submit_time
It's worth having a quick look at: https://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html - to understand what is meant by a callback. Basically, this method is going to get automatically called whenever a new record is created. (You probably could have guessed this, based on the fairly self-explanatory name: before_create!)
Again, the first example on that page is almost identical to your scenario. You can use Time.now to get the current time.
If you're still stuck on this problem, please show us what you've tried.
I'm working with a massive legacy code base, so I am looking for advice concerning this particular issue, please, not suggestions of better high-level implementations.
A simplified version of what I'm working with:
class Order < ActiveRecord::Base
has_many :line_items
#other stuff
def balance
#some definition
end
end
class LineItem < ActiveRecord::Base
belongs_to :order
#other stuff
end
module Concerns
module LineItems
module Aggregates
extend ActiveSupport::Concern
#stuff
def balance
#some other definition
end
end
end
end
Order has a method called 'balance,' and a module of LineItem also has a method called 'balance.' It seems that most of the time (in most places in the code base), when specific_line_item.balance is called, it used the method definition under the LineItem module, but there are a couple of places where it instead calls the method from Order.
Is there any way in Ruby/Rails to specify on method call which of these two I'd like to use? OR is there probably something else going on here because Ruby doesn't have method overloading, so the problem I'm describing here isn't possible?
All relevant cases where either method is called are coming from a line_item (i.e. specific_line_item.balance), so I would think it would always choose the method closer to home, rather than making the associative jump and calling Order's 'balance' method without being told to.
EDIT:
Thanks for the responses! It seems I wasn't clear enough with my question. I understand the difference between
Order.first.balance
and
LineItem.first.balance
and that the balance method being called is the one defined within the class for that object. In the situation I'm describing, I observed, in the actual live app environment, that at a place in the code where
LineItem.find(some_id).balance
was called it output not the result that would be computed by the LineItem 'balance' method, but the one from the Order class.
So I had hoped to learn that there's some ruby quirk that might have an object call an associate's method of the same name under some conditions, rather than it's own. But I'm thinking that's not possible, so there's probably something else going on under the covers specific to this situation.
Firstly, ActiveRecord::Concern can change a lot of behaviour and you've left out a lot of code, most crucially, I don't know where it's being injected, but I can make an educated guess.
For a Concern's methods to be available a given object, it must be include'd in the object's class's body.
If you have access to an instance of the Order object, at any point you can call the balance method:
order = Orders.last # grab the last order in your database
order.balance # this will call Order#balance
And if you have the Order then you can also get the LineItem:
order.line_items.first.balance # should call the Concerns:: LineItems::Aggregates#balance
You can open up a Rails console (with rails console) and run the above code to see if it works as you expect. You'll need a working database to get meaningful orders and balances, and you might need to poke around to find a completed order, but Ruby is all about exploration and a REPL is the place to go.
I'd also grep (or ag or ack) the codebase looking for calls to balance maybe doing something like grep -r "(^|\s)\w+\.balance" *, what you want to look for is the word before .balance, that is the receiver of the "balance" message, if that receiver is an Order object then it will call Order#balance and if it is a LineItem object then it will call Concerns:: LineItems::Aggregates#balance instead.
I get the feeling you're not familiar with Ruby's paradigm, and if that's the case then an example might help.
Let's define two simple Ruby objects:
class Doorman
def greet
puts "Good day to you sir!"
end
end
class Bartender
def greet
puts "What are you drinking?"
end
end
Doorman and Bartender both have a greet method, and which is called depends on the object we call greet on.
# Here we instantiate one of each
a_doorman = Doorman.new
a_bartender = Bartender.new
a_doorman.greet # outputs "Good day to you sir!"
a_bartender.greet # outputs "What are you drinking?"
We're still using a method called greet but the receiver is what determines which is called.
Ruby is a "message passing language" and each "method" is not a function but it's a message that is passed to an object and handled by that object.
References
How to use concerns in Rails 4
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
http://guides.rubyonrails.org/command_line.html#rails-console
I am not sure how to go about this, or if there is a better way to do this, but I have a table called leads(many) and it references agent (one).
I want to make a migration that sets a default value for the agent_id column in the leads table. But i want the default value to loop through all the agent ids. Im not sure how to do this!? Should i use a call back, or should i do in migration file?
Here is the actual question im trying to tackle:
When new leads are created assign it to an agent using a “round robin” That way new leads are distributed evenly across all the agents.
Ive attached a screenshot using SUDO code (i know its not functional as is) as to what I am thinking of doing. Any tips?
(Using ruby on rails w/ postgresql)
I think it makes sense to handle this functionality as part of the main app, and not within migration, as there seem to be a significant chunk of functionality to handle.
Probably best to handle it as part of an after_create callback in the Lead model, and use a class variable to track the next agent to be assigned as follows:
class Lead
# Assign the class variable to the first agent
##next_agent = Agent.first
after_create :set_agent
...
private
# Called by the after_create callback
# Sets the agent_id, and updates the ##next_agent class variable
def set_agent
self.agent_id = ##next_agent.id
##next_agent = find_next_agent
end
## Called from the set_agent method
## Finds the next agent based on the current value of ##next_agent
def find_next_agent
##next_agent = Agent.find(##next_agent.id + 1)
##next_agent = Agent.first unless #next_agent
end
end
The find_next_agent logic above is a simplistic example, assuming that all Agent objects have ids that increment by 1, and there are no gaps (i.e. no deletions in the table).
Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)