Rails how to randomly pick a row in a table? - ruby-on-rails

How do I random pick a row in a table?
Example my table:
name age
Lars 24
Grete 56
Hans 56
I want to random pick a name.
Example:
#randomname = Model.where(:name 'Random')
And how do I only pick 1 random name each day. That I can use as a instant variable in view.

#randomname = Model.order('rand()').limit(1).first.name

A random column in Rails 3 is simply:
Model.columns.sample.name

If you want to get random object 1 per day, so you should store it somewhere. You can store it:
in separate file
in separate model
in the same model add rndm field
Lets implement the last one. It is quite easy. Imagine, that your Model called User. First, let's add new date field rndm:
rails g migration add_rndm_to_user rndm:date
rake db:migrate
Now we need to add some methods to your User model
class User < ActiveRecord::Base
def self.random
rndm = find_by_rndm Date.today
unless rndm
update_all :rndm => nil
rndm = self.order('rand()').first
rndm.update_attribute :rndm, Date.today
end
rndm
end
end
so now you can call User.random from your controller

I have some Rails 2 code that shows the idea i think, and it's very easy to convert to rails 3 :
random_monsters = self.find(:all, :conditions=>["level > 0 and level < ? and monster_type='monster'", user_level+2])
random_monster = random_monsters[rand(random_monsters.length)]
I've also seen somebody in SO propose an offset way like :
offset = rand(Model.count)
rand_record = Model.first(:offset => offset)

Related

Storing values in array or additional table

I'm developing a booking system in rails 5.1 for hotels, whereas the price per night depends on the duration of a stay. Ok, so basically this is a question related to database design and I would like your opinion on which option to go with:
Option A: I just go ahead and save the prices in my Room table in an array, so that:
price_increments = [1_day,2_days,...,n_days] = [80,65,...,x]
I could then access this array by passing the duration of stay, so that:
def booking
days = (end_date - start_date).to_i
price = price_increments.at(days)
total = price * days
end
Option B: I create an additional table for prices, but then I wouldn't be quite sure how to access the respective price with regards to the duration, especially since the application is supposed to be a platform with multiple hotels?
What do you think? Is it save to go with Option A or shall I try to go with Option B? What would be considered to be best practice? Any advice appreciated:)
For a good reason you should develop your logic price_increments record: we need to know if it's a regular price or with major.
Any way i think it's a possible to do your opération before saving record in a singular table with before_save
def Booking < ActiveRecord::Base
before_save :init
PRICE_INCREMENT = [one, two, three, four]
def init
self.duration ||= (end_date - start_date).to_i
self.price ||= duration * PRICE_INCEREMENT[duration]
end
end

Rails show total column when data comes from model method

I have a model Invoices
Table Invoices:
ID, PRODUCT, UNITS, PRICE
In my view, I want to show total for some columns.
For instance to show total quantity I can set in my controller:
#invoices = Invoice.group(:PRODUCT).select(ID, PRODUCT, UNITS, SUM(UNITS) AS TOTALUNITS, PRICE").order('PRODUCT ASC')
#units_total = #invoices.map(&:UNITS).sum
and in my view
<%= #units_total %>
and it returns the total in column UNITS. This works fine.
If I define in my model:
def total_amount
(self.PRICE * self.TOTALUNITS)
end
and then on the same way I want to show the total of total_amount, I tried in my controller:
#amount_total = #invoices.map(&:total_amount).sum
it doesn't work, as I assume that if I'm not using a column name the syntax must be different.
What should I enter then?
UPDATE
The problem comes form the fact that I'm using in model (self.PRICE * self.TOTALUNITS). I didn't include it when I posted the question as I thought it didn't matter. But if I replace(self.PRICE * self.TOTALUNITS) with (self.PRICE * self.UNITS) there's no error but values are obviously wrong.
What you have taken is correct, I think some of the invoices doesnot have the value, check using try,
def total_amount
price = self.try(:PRICE) || 0
units = self.try(:UNITS) || 0
price * units
end
make 0 if the corresponding field value is absent, so that you will not get an error.
You can check if this is working or not
#amount_total = #invoices.map{ |invoice| invoice.PRICE.to_f * invoice.UNITS.to_i }.sum
I don't know what error you are getting but it might be due to null values in the column.
Hope that helps!

Act_As_Votable with Reddit style weighting algorithm in Rails

I am creating a rails app that has a User and Post model that implements the Act_As_Votable gem.
I want users to be able to upvote and downvote posts, but also want to rank and sort posts by a weighted_score algorithm that takes into account the number of upvotes, downvotes, and time the post was created.
My weighted_score algorithm is taken from Reddit and better described here.
My Post Model:
class Post < ActiveRecord::Base
belongs_to :user
acts_as_votable
# Raw scores are = upvotes - downvotes
def raw_score
return self.upvotes.size - self.downvotes.size
end
def weighted_score
raw_score = self.raw_score
order = Math.log([raw_score.abs, 1].max, 10)
if raw_score > 0
sign = 1
elsif raw_score < 0
sign = -1
else
sign = 0
end
seconds = self.created_at.to_i - 1134028003
return ((order + sign * seconds / 45000)*7).ceil / 7.0
end
end
I want to use the Acts_As_Voteable gem because it supports caching which may decrease the number of hard disk writes and save time. Currently the weight_score of a post can be calculated on the fly but is not saved in the database, meaning I cannot do database sorts on posts with the highest weighted_score.
If I created a column in the post model I would have to update the posts table every time a user voted on a post, which defeats the purpose of using the Acts_As_Tagable gem (as I don't take advantage of its caching ability).
So I want to add a column to the votes table to store the weighted_score (which will then be calculated every time the post is voted on), as well as a method to the Votes model to calculate this score, however the gem does not provide a model when I run its generator. It only creates a votes table which I do not know how to access without a model.
Any help on how I can add such a weighted_score column and method to the votes model, or on how to achieve efficiently storing the weighted score of a post in a different manner is appreciated.
acts_as_voteable adds methods to your model to access the votes
http://juixe.com/techknow/index.php/2006/06/24/acts-as-voteable-rails-plugin/
positiveVoteCount = post.votes_for
negativeVoteCount = post.votes_against
totalVoteCount = post.votes_count
If you want to add a column, you can run a migration as normal on the table it creates. It also does appear to create a Vote model http://juixe.com/svn/acts_as_voteable/lib/vote.rb
I would add the weighted_score column to your Post model and handle updating via callback. For instance:
class Post < ActiveRecord::Base
#...
before_save :update_weighted_score
#...
def update_weighted_score
# check if some relevant variables have changed first, for example
if cached_votes_total.changed?
# do maths
weighted_score = blah
end
end
You can do this with MYSQL out of the box with decent results, used multi-line for easier readability.
Post.order("
LOG10( ABS( some_score ) + 1 ) * SIGN( some_score ) +
( UNIX_TIMESTAMP( created_at ) / 450000 ) DESC
")
450000 is the number to tweak that will given more weighting to the score vs. the created_at.
Closer to zero gives more weight to the new-ness.
45000 will roughly return scoring for the day
450000 will roughly return scoring for the week
4500000 will roughly return scoring for the month

Copy/Clone objects from database

I have relations as follows :
Customer has many Orders
Order has many Parts
What I'm trying to do is to assign orders/parts to another customer, but I want to keep the original customer/orders/parts relation. So I tried this (assuming customer is a Customer instance)
another_customer_id = 22
customer.orders.each do |order|
ord = order.dup
ord.customer_id = another_customer_id
ord.save!
order.parts.each do |part|
prt = part.dup
prt.order_id = ord.id
prt.save!
end
end
So I'm assuming once I do customer_two.orders I will get everything as in first customer, but strangely I don't. It seems the first customer has double the orders/parts.
How can I do this in a better way?
update
When I use .dup method the original customers orders are doubled in the database.
When I use .clone instead of .dup nothing happens, another customer still doesn't have orders from original customer
The code you have here looks reasonable to me if you are using Rails >= 3.1. Before Rails 3.1 you need to use clone instead of dup. However, here is another method that you can try:
another_customer_id = 22
customer.orders.each do |order|
ord = Order.create(order.attributes.merge(:customer_id => another_customer_id))
order.parts.each do |part|
Part.create(part.attributes.merge(:order_id => ord.id))
end
end

What's the most efficient way to keep track of an accumulated lifetime sales figure?

So I have a Vendor model, and a Sale model. An entry is made in my Sale model whenever an order is placed via a vendor.
On my vendor model, I have 3 cache columns. sales_today, sales_this_week, and sales_lifetime.
For the first two, I calculated it something like this:
def update_sales_today
today = Date.today.beginning_of_day
sales_today = Sale.where("created_at >= ?", today).find_all_by_vendor_id(self.id)
self.sales_today = 0
sales_today.each do |s|
self.sales_today = self.sales_today + s.amount
end
self.save
end
So that resets that value everytime it is accessed and re-calculates it based on the most current records.
The weekly one is similar but I use a range of dates instead of today.
But...I am not quite sure how to do Lifetime data.
I don't want to clear out my value and have to sum all the Sale.amount for all the sales records for my vendor, every single time I update this record. That's why I am even implementing a cache in the first place.
What's the best way to approach this, from a performance perspective?
I might use ActiveRecord's sum method in this case (docs). All in one:
today = Date.today
vendor_sales = Sale.where(:vendor_id => self.id)
self.sales_today = vendor_sales.
where("created_at >= ?", today.beginning_of_day).
sum("amount")
self.sales_this_week = vendor_sales.
where("created_at >= ?", today.beginning_of_week).
sum("amount")
self.sales_lifetime = vendor_sales.sum("amount")
This would mean you wouldn't have to load lots of sales objects in memory to add the amounts.
You can use callbacks on the create and destroy events for your Sales model:
class SalesController < ApplicationController
after_save :increment_vendor_lifetime_sales
before_destroy :decrement_vendor_lifetime_sales
def increment_vendor_lifetime_sales
vendor.update_attribute :sales_lifetime, vendor.sales_lifetime + amount
end
def decrement_vendor_lifetime_sales
vendor.update_attribute :sales_lifetime, vendor.sales_lifetime - amount
end
end

Resources