We're building an intranet in Ruby on Rails and now we want to add functionality for having reminders when you should have done something but haven't.
For example, say you have a model, Meeting, I want to send reminders to everyone who has had a meeting but where the meeting report is empty (meeting.date < Date.today && meeting.report == ""). And a project model where you have some other criteria (maybe project.last_report.date < lastMonday && !project.closed)
Now, I can imagine creating these reminders with some kind of rake task and then removing them by some event trigger when you update Meeting or whatever, but that seems like I'll get spaghetti code everywhere.
My second idea is to make a separate module that, on each page load, fetches all the entries that could be related and runs all these checks, and then returns Reminders, however, this will probably be hella slow to hit the database like this. (Maybe caching would be an option but still)
So, anybody done something like this and have any ideas on how to solve our problem?
Thanks!
I can't see any issue with spaghetti code if you let each object that requires a Reminder to manage it's own reminders. If you want to be an OOP purist you could probably create a separate class (e.g., MeetingReminderManager in your example) to manage the reminders but that seems like overkill here. Consider...
class Reminder
belongs_to :source, polymorphic: true
belongs_to :user
end
class Meeting
has_many :reminders, as: :source
has_many :users
after_create :build_reminders, if: ->{|meeting| meeting.report.blank? }
after_update :destroy_reminders, if: ->{|meeting| !meeting.report.blank? }
private
def build_reminders
users.each{|user| self.reminders.create user_id: user.id, due_on: self.date }
end
def destroy_reminders
self.reminders.delete_all
end
end
I don't see a problem with spaghetti and background job in ruby on rails. I think making them is the path to go. Check whatever is suit you: http://railscasts.com/?tag_id=32
Related
I've read on counter caches on associations.
Is there a way to easily implement (via a gem that will do the heavy lifting) of a counter cache for some condition? for example:
usually a counter cache would be
class User
has_many :messages
class Message
belongs_to :user, counter_cache: true
however, lets say that I don't want to count how many messages, but to count the total number of characters in all of the messages from Joe
so lets say I have a method count_chars_from(user) that returns the number of chars from a user
I want to update a specific column when many thing occur in the system (when joe sends a message to several people - all of those users need to be updated, when joe edits a message to one person, etc)
This could be done with observers I guess, but I see myself creating ugly code very quickly.
Is there a way to have something like the above?
Currently, there is no special methods/helpers for this. Rails doesn't support conditional counters.
However, you can simply define your own.
class Message
belongs_to :user
after_create :inc_counters
after_destroy :dec_counters
private
def inc_counters
if user && your_conditions
user.increment(:conditional_counter)
end
end
def dec_counters
if user && your_conditions
user.decrement(:conditional_counter)
end
end
end
I'd suggest take a look at counter_cache implementation. It looks pretty complex, but simple implementation works in one of my project pretty well.
Also consider to use update_column to not trigger validations and callbacks on User model.
And test you code when you delete parent model if it has dependent: :destroy on association.
I have a Game model which has_many :texts. The problem is that I have to order the texts differently depending on which game they belong to (yes, ugly, but it's legacy data). I created a Text.in_game_order_query(game) method, which returns the appropriate ordering.
My favourite solution would have been to place a default scope in the Text model, but that would require knowing which game they're part of. I also don't want to create separate classes for the texts for each game - there are many games, with more coming up, and all the newer ones will use the same ordering. So I had another idea: ordering texts in the has_many, when I do know which game they're part of:
has_many :texts, :order => Text.in_game_order_query(self)
However, self is the class here, so that doesn't work.
Is there really no other solution except calling #game.texts.in_game_order(#game) every single time??
I had a very similar problem recently and I was convinced that it wasn't possible in Rails but that I learned something very interesting.
You can declare a parameter for a scope and then not pass it in and it will pass in the parent object by default!
So, you can just do:
class Game < ActiveRecord
has_many :texts, -> (game) { Text.in_game_order_query(game) }
Believe or not, you don't have to pass in the game. Rails will do it magically for you. You can simply do:
game.texts
There is one caveat, though. This will not work presently in Rails if you have preloading enabled. If you do, you may get this warning:
DEPRECATION WARNING: The association scope 'texts' is instance dependent (the scope block takes an argument). Preloading happens before the individual instances are created. This means that there is no instance being passed to the association scope. This will most likely result in broken or incorrect behavior. Joining, Preloading and eager loading of these associations is deprecated and will be removed in the future.
Following up using PradeepKumar's idea, I found the following solution to work
Assuming a class Block which has an attribute block_type, and a container class (say Page), you could have something like this:
class Page
...
has_many :blocks do
def ordered_by_type
# self is the array of blocks
self.sort_by(&:block_type)
end
end
...
end
Then when you call
page.blocks.ordered_by_type
you get what you want - defined by a Proc.
Obviously, the Proc could be much more complex and is not working in the SQL call but after there result set has been compiled.
UPDATE:
I re-read this post and my answer after a bunch of time, and I wonder if you could do something as simple as another method which you basically suggested yourself in the post.
What if you added a method to Game called ordered_texts
def ordered_texts
texts.in_game_order(self)
end
Does that solve the issue? Or does this method need to be chainable with other Game relation methods?
Would an Association extension be a possibility?
It seems that you could make this work:
module Legacy
def legacy_game_order
order(proxy_association.owner.custom_texts_order)
end
end
class Game << ActiveRecord::Base
includes Legacy
has_many :texts, :extend => Legacy
def custom_texts_order
# your custom query logic goes here
end
end
That way, given a game instance, you should be able to access instance's custom query without having to pass in self:
g = Game.find(123)
g.texts.legacy_game_order
Here is a way where you can do it,
has_many :texts, :order => lambda { Text.in_game_order_query(self) }
This is another way which I usually wont recommend(but will work),
has_many :texts do
def game_order(game)
find(:all, :order => Text.in_game_order_query(game))
end
end
and you can call them by,
game.texts.game_order(game)
Im not sure what your order/query looks like in the in_game_order_query class method but i believe you can do this
has_many :texts, :finder_sql => proc{Text.in_game_order_query(self)}
Just letting you know that I have never used this before but I would appreciate it if you let me know if this works for you or not.
Check out http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many for more documentation on :finder_sql
I think if you want runtime information processed you should get this done with:
has_many :texts, :order => proc{ {Text.in_game_order_query(self)} }
I am new to rails so beware of the ugly code.
I have these models
class User < ActiveRecord::Base
has_many :games_playeds
has_many :games, :through => :games_playeds
end
class Game < ActiveRecord::Base
has_many :games_playeds
has_many :users, :through => :games_playeds
end
class GamesPlayed < ActiveRecord::Base
validates_presence_of :user_id, :game_id
belongs_to :user
belongs_to :game
end
Game describe a game independent of any user
GamesPlayed describe how users behaved on that game (deaths, current stage, wins, etc)
In each stage of this game the user can choose among several choices, some will get to later stages, some will make him go back. The point is that once a choice is made in one stage I don't allow to choose anything else.
To implement this I have a steps attribute that encode the previous choices like "0:1;1:6;6:2" and so on. This attribute in on the GamesPlayed model.
The pages the user navigates are automatically generated so I don't know their names but I know they are called XX_to_YY. I have a method in my controller that will get them all and do something as ugly as this:
#get the game name, we have several games
game = Game.find_by_name (params[:game])
#get the previous and current stage
from, to = params[:page].to_s.split("_to_")
to = to.split(".html")[0]
played = current_user.games_playeds.find_by_game_id (game.id)
steps = []
played.steps.split(";").each {|a| steps << a.split(":").first}
if steps.include? from
render :inline => "You already chose for this, go back"
else
played.steps << "#{from}:#{to};"
played.save
# pl = current_user.games_playeds.find_by_game_id (game.id)
# raise pl.steps
render "games/choosePath/#{game.name}/#{params[:page]}.html"
end
I guess it is a horrible code. I am new to Ruby as well :P
Now, the question:
played.save gives me no errors.
# pl = current_user.games_playeds.find_by_game_id (game.id)
# raise pl.steps
will "print" the correct data but it is not saved in the database! I use sqlitebrowser to visually inspect it and I am sure it is not saved.
BTW, as second question, if someone knows how to get to the association object without that ugly code above very thankful also.
and third and last question:
steps = []
played.steps.split(";").each {|a| steps << a.split(":").first}
This is also horrible but don't know how to make it better (want to get the aa and bb from "aa:cc;bb:dd;" I don't know what is aa and bb, it can be numbers or words.
If you want to raise an exception when save fails, call save!; otherwise if you continue to use save you should check the returned boolean to see if the save was successful.
A false return value will indicate that validations have failed. The details of the failures will be in the error information on the model.
About getting the association in a better way: there is probably something you could do with scopes or even just by writing a method to encapsulate what you are trying to do.
With regard to decoding the steps, you could use inject instead of each but it would still be quite logic heavy. I would suggest encapsulating it in a method with a descriptive name, like decode_steps or similar.
I have states who have many cities (belongs_to :state) who have many businesses (belongs_to :city).
State also… has_many :businesses, :through => :cities
On my site everything is managed from the Business perspective. When a new Business is created/updated the state/city is created if it doesn't already exist. This happens in a :before_save call.
I'm having problems removing States/Cites when a Business gets updated. If the state/city that a business is in gets changed (again this happens from an edit business form) and the old state/city no longer has any businesses I want to destroy it. I've tried doing this in after_save calls but they're wrapped in a transaction and even if I assign variables to the names of the old state/city, they seem to get changed to the new state/city sometime during the transaction. It's crazy! I used "puts" calls to print the vars in some spots in my Business model and watched the vars change during a save. It was frustrating.
So, right now I'm handling this from the controller but it feels hackish.
Here's some of my code.
http://pastie.org/648832
Also, I'd love any input on how better to structure this whole thing.
Thanks
You want after_destroy callbacks to destroy the has many side of a relationship if it has none.
To ensure this behaviour after an update, we need to use the ActiveRecord::Dirty methods. Which are built into rails as of 2.1. If you're running an older version you'll need the Dirty plugin
class Business < ActiveRecord::Base
...
after_update :destroy_empty_city
after_destroy :destroy_empty_city
protected
def destroy_empty_city
c = city_changed? ? city_was : city
c.destroy if c.businesses.empty?
end
end
class City < ActiveRecord::Base
...
after_destroy :destroy_empty_state
protected
def destroy_empty_state
state.destroy if state.businesses.empty?
end
end
You might need to check if city/state.businesses == [self] instead of city/state.businesses.empty? if your associations are eager loaded. I can't remember how rails treats associations after destroy. I'm assuming that if they're eager loaded than the code above won't work and you will need the alternate check. Otherwise it should be fine.
Well, I don't know if I'm completely wrong, but I really can't find a very practical and straight forward way to do something like this:
class User < ActiveRecord::Base
has_many :creations
end
but the thing is, I just want the user to have many creations if the user.developer == true
where user.developer is just a boolean field inside the Users table.
So any ideas on how exactly could I do it directly from the model?
Resuming, when the user is not a developer if you try to get User.first.creations, User.first.creations.new ... create...destroy, etc you get a NoMethodError but if it is a developer you can build a new creation.
The only way I managed to do it is extending the model and from the extension check if the proxy_owner.developer == true but by doing this I had to rewrite all the actions such new, create, update, etc...
Any help would be much appreciated
Thanks a lot
How about subclassing User and only specifying the has_many on the developer subclass? Developer would then pick up any logic from User and Users wouldn't have any creations.
class User < ActiveRecord::Base
end
class Developer < User
has_many :creations
end
Including this may work. If not you may have to resort to alias_method_chain, but I hear that has links to serious organised crime, so watch yourself.
module CreationsJustForDevelopers
def creations(*args)
if developer?
super
else
raise NoMethodError, "Only developers get creations."
end
end
end
Not sure what you are referring to with all that talk of overriding new, create, update etc… but the only other method I can think of to remove is creation_ids, but who cares about that?