I have to define an association that doesn't seem to fit in well to the "has_one / belongs_to" bucket very well.
The situation is this, I have a table whereby each row corresponds to monthly statistics for a given month and year. I'd love to be able to define certain associations on my model such as record.prior_month or record.prior_year which would correspond to the prior month / year of the current record.
I can't think of any clever way to do this as it doesn't make any sense to maintain foreign keys that would have to be updated every month for tons of records.
I can always handle the logic in the controller, but I'd prefer to keep it with the model if I could.
Thanks!
Mike
So rather than store the Month/Year, also store the Month+Year*12. So March 2011 is 24135
That way, you know the next month is 21436, and you can easily paginate over your records.
TrafficGroup.order("month_calculated").paginate(:page=>params[:page])
Something like this?
class MyModel < AR::Base
def prior_month
created_at.month
end
def prior_year
created_at.year
end
end
example = MyModel.last
example.prior_year
#=> 2010
example.prior_month
#=> 3
You can do this a few ways. Assuming the month is stored in the model.
My favorite is scopes, since it plays nicely with other associations.
For instance you can do:
class TrafficRecord < ActiveRecord::Base
scope :only_month, lambda {|month| where :month => month} # this should also contain join conditions
def prior_month
self.class.only_month(self.month - 1) #decrement your month
end
end
Related
In my Rails application i have a Shop model.
I need to store weekdays and corresponding opening hours like:
Monday 18:00 - 2:00
Tuesday 19:00 - 1:00
etc..
I expect to have a lot of shop records and i'm wondering what is the best way to store it provided that i care a lot about quick access.
One way is to make use of Postgresql hstore type.
I would keep a hash like "Monday" => "18:00-2:00", "Tuesday"=>...
in an oppening_hours column in a shop table
Or I can solve it with a simple relational approach
class Schedule < ApplicationRecord
#weekday:string, start_at:datetime, end_at:datetime
belongs_to :shop
end
class Shop < ApplicationRecord
has_one :schedule
#...
end
But then, any time i need to show info about shop i have to query 2 tables.
I am not sure if this is the optimized solution when talking about big table that is about to grow rapidly.
I would appreciate some advice.
You may use the native ARRAY type. It is storage-efficient and will probably have decent support in ORMs.
https://www.postgresql.org/docs/9.4/static/arrays.html
I would consider something like:
CREATE TABLE shop (
...
schedule text[][3]
);
INSERT INTO shop
VALUES (...
'{{"Monday", "18:00", "2:00"}, {"Tuesday", ...}}');
I would use your second approach but with some differences
class Schedule < ApplicationRecord
#weekday:integer, start_at:datetime, end_at:datetime
belongs_to :shop
end
class Shop < ApplicationRecord
has_many :schedule
#...
end
Here some consideration about this solution:
First of all, I think it is suitable to store the start and end date in different columns. The reason is that you will be able to perform queries like "give me all shops which are open now" you will not have to parse each value in the column "opening_interval".
I would use a number to store the day of the week. It seems intuitive that, when querying the database, it is easier to compare a column with a number than comparing it to a string.
I think your relationship should be one-to-many in order to save up to 7 schedules for each shop.
I would add an ActiveRecord validation to check that the weekday cannot be repeated for the same shop (a shop should not have two different opening intervals for the same day)
A Miniatures model has many Collections. Users can have and vote for the best Collection version of a Miniature. The votes are in a model called Imagevotes which update a counter_cache attribute in the Collections model.
What I want to do is flag Collections which are ranked first for a Miniature as GOLD, then rank the 2nd, 3rd and 4th as SILVER. I realise I can do this on the Miniature model by selecting the #miniature.collection.first, but I would like to be able to store that like you would store the vote-count in a counter_cache so that I could display the total number of GOLDS or SILVERS for any one user.
Is there a way that each model could have Boolean fields called GOLD and SILVER which would be updated as new votes are cast in the same way that a counter_cache is updated?
Any pointers and further reading much appreciated.
Update:
It occurs to me that this could also be done with a sort of second index column. A vote_position column if you will, that updated with a number from "1" for the record with the highest counter_cache number and ascended from there. Then I could use #miniature.collection.where(:vote_position => "1") or similar. Perhaps this is more ideal?
As it seems for me you just need to implement method in Miniature model:
def set_gold_and_silver
top_collections = self.collections.order("imagevotes_count desc").limit(4)
gold = top_collections.shift
gold.update_attribute :is_gold, true if gold
top_collections.each {|s| s.update_attribute :is_silver, true}
end
after that you can add it to after_create filter of Imagevotes model:
class Imagevotes < ActiveRecord::Base
after_create :set_gold_and_silver
def set_gold_and_silver
self.miniature.set_gold_and_silver
end
end
I'm a bit of a noob programmer so apologies if the question isn't clear enough.
I'm trying to create a basic rails app where I have 3 different tables: usages(month, usage), prices(month, price) and spends(month, spend).
I'm trying to get it so that spend = usages.usage * prices.price. I've put the following code into my Spend model:
class Spend < ActiveRecord::Base
c = Usage.all.count
i = 1
while i <= c
u = Usage.find(i)
p = Price.find(i)
Spend.create(month:u.month, spend:u.usage*p.price)
i += 1
end
end
This works great initially, but as soon as I start adding and removing usages and prices, their id's change so it isn't as clear cut. How can I do this in a much better way?
Thanks,
Kev
In this case, I would lean against making a separate Spend model, since all it does is calculate data that is already present in the database. Unless you have severe caching requirements (and I doubt it in your case), you can use simple instance methods to retrieve the data you want.
First figure out how your Usage and Price models are related. Since you seem to be associating them by id, it appears to be a one-to-one relationship (correct me if I'm wrong on this). However, associating by assuming they have the same primary key is a dangerous approach - rather have one model point to the other using a foreign key. We'll pick the Price model to hold a primary key for Usage, but the reverse can also work. You'll need to add a column using a migration like this:
def change
add_column :prices, :usage_id, :integer
end
Your models should then look like this:
class Usage < ActiveRecord::Base
has_one :price
def spend
usage * price.price
end
end
class Price < ActiveRecord::Base
belongs_to :usage
end
And you can find your spend value for an individual usage item like this:
usage = Usage.find(some_id)
puts usage.spend
Or you can get multiple 'spends' like this:
Usage.include(:price).each do |usage|
puts usage.spend
end
I've left out any reference to month, as I'm not sure how you are using it or if it's needed at all for calculating spend.
Have a look at the Active Record association guide: http://guides.rubyonrails.org/association_basics.html
I've got a Match model and a Team model.
I want to count how many goals a Team scores during the league (so I have to sum all the scores of that team, in both home_matches and away_matches).
How can I do that? What columns should I put into the matches and teams database tables?
I'd assume your Match model looks something like this:
belongs_to :home_team, class_name:"Team"
belongs_to :away_team, class_name:"Team"
attr_accessible :home_goal_count, :away_goal_count
If so, you could add a method to extract the number of goals:
def goal_count
home_matches.sum(:home_goal_count) + away_matches.sum(:away_goal_count)
end
Since this could be expensive (especially if you do it often), you might just cache this value into the team model and use an after_save hook on the Match model (and, if matches ever get deleted, then an after_destroy hook as well):
after_save :update_team_goals
def update_team_goals
home_team.update_attribute(:goal_count_cache, home_team.goal_count)
away_team.update_attribute(:goal_count_cache, away_team.goal_count)
end
Since you want to do this for leagues, you probably want to add a belongs_to :league on the Match model, a league parameter to the goal_count method (and its query), and a goal_count_cache_league column if you want to cache the value (only cache the most recently changed with my suggested implementation, but tweak as needed).
You dont put that in any table. Theres a rule for databases: Dont ever store data in your database that could be calculated from other fields.
You can calcuate that easyly using this function:
def total_goals
self.home_matches.collect(&:home_goals).inject(&:+)+self.away_matches.collect(&:away_goals).inject(&:+)
end
that should do it for you. If you want the mathes filtered for a league you can use a scope for that.
I'm doing some statics calculation in my product. A user has performed a number of operations, let's say posted comments. I want to be able to show them how many comments they've posted per week for the past month, or per month for the past year.
Is there any way with activerecord to group this way? Is my best best to simply do this manually - to iterate over the records summing based on my own criteria?
class User < ActiveRecord::Base
has_many :comments
end
class Comments < ActiveRecord::Base
belongs_to :user
end
#user.comments(:all).map {|c| ...do my calculations here...}
or is there some better way?
thanks!
Oren
In Postgres you can do:
#user.comments.group("DATE_TRUNC('month', created_at)").count
to get:
{"2012-08-01 00:00:00"=>152, "2012-07-01 00:00:00"=>57, "2012-09-01 00:00:00"=>132}
It accepts values from "microseconds" to "millennium" for grouping:
http://www.postgresql.org/docs/8.1/static/functions-datetime.html#FUNCTIONS-DATETIME-TRUNC
In this case, the best solution for me was to either do it in straight SQL, or to use the Ruby group_by function:
#user.all.group_by{ |u| u.created_at.beginning_of_month }
Here is the more refined version of this
#user.comments.group("year(created_at)").group("month(created_at)").count
My guess would be something like:
#user.comments.count(:group => "year(created_at),month(created_at)")
Dry-code, ymmv
Use group_by
#user.comments.group_by(&:week)
class User < ActiveRecord::Base
def week
some_attribute_like_date.strftime('%Y-%W')
end
end
This will give you a grouped list in the format of YYYY-WW
Check out the has_activity plugin.
Check out the group date gem
https://github.com/ankane/groupdate
it has recent commits, works with postgresql, integrates easily with chart kick for fast charting, and works with time zones!!