Summing Mongoid BigDecimal - ruby-on-rails-3

Mongoid 3 documentation shows you can do simple sums using something like this: Band.sum(:likes)
I have the following simple models:
class Project
...
has_many :subprojects
...
end
class Subproject
...
field :subtotal, :type => BigDecimal, :default => 0
...
end
How do I sum subtotal across each Project?
For example, I tried
Project.first.subprojects.sum(:subtotal) it returns 0.
But
Project.first.subprojects.first.subtotal returns #<BigDecimal:7fcb0d77b958,'0.11054E3',18(18)>
Any suggestions?

I tested how Mongoid stores BigDecimal:
rails c:
class Test
include Mongoid::Document
field :decimal, type: BigDecimal
end
test = Test.new
test.decimal = BigDecimal.new(123, 456)
test.save
I then queryed the database:
> db.tests.find()
{ "_id" : ObjectId("52e0ed4bb2e8c9ea34000001"), "decimal" : "123.0" }
You clearly see that the BigDecimal is stored as a string, thus explaning why you can't sum it.
Howevery, you can try to parse it then sum it using a map/reduce :
map = %Q{
function() {
emit("BigDecimalSum", { decimal: parseFloat(this.decimal) });
}
}
reduce = %Q{
function(key, values) {
var result = { sum: 0 };
values.forEach(function(value) {
result.sum += value.decimal;
});
return result;
}
}
Test.map_reduce(map, reduce).out(inline: true)
I've just tester the parseFloat in mongo client and it works, so this map/reduce should work too.

To sum BigDecimal fields in Mongoid you use the block form of #sum:
sum = 0
Project.first.subprojects.sum do |subproject|
sum += subproject.subtotal
end

Related

Rails: Search by custom instance method's value using tire gem & elasticsearch

For example, I have Article model like
class Article < ActiveRecord::Base
#Columns: id, title, status_number...etc
STATUSES = {1 => "SUCCESS", 2 => "REJECTED"}
include Tire::Model::Search
include Tire::Model::Callbacks
def display_status
STATUSES[status_number]
end
def self.search(params)
tire.search(load: true, page: params[:page], per_page: 2) do
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
end
end
end
end
how to include display_status as "SUCCESS" by default in search method?
I tried
query do
boolean do
must { string params[:query], default_operator: "AND" } if params[:query].present?
must { term :display_status , "SUCCESS" }
end
end
But couldn't get result.
Please help to solve this problem. Thanks

Yii2 - custom order in the models get relationship

What is the correct way to make the custom order in the models get relationship? Is it possible to do this? This is my current code:
public function getBoardPosts()
{
return $this->hasMany(BoardPosts::className(), ['topic_id' => 'id'])->orderBy('order ASC');
}
Yes it is. Here is an example from the guide:
class Customer extends ActiveRecord
{
public function getBigOrders($threshold = 100)
{
return $this->hasMany(Order::className(), ['customer_id' => 'id'])
->where('subtotal > :threshold', [':threshold' => $threshold])
->orderBy('id');
}
}
Please note that you may have to quote the field (or better yet name your columns using non-SQL words):
return $this->hasMany(BoardPosts::className(), ['topic_id' => 'id'])->orderBy('`order` ASC');

How to transform sort_by with conditions into a rails scope

I'm trying to change self.rank:
def self.rank(courses)
courses.sort_by do |course|
[course.list_order ? course.list_order : Float::INFINITY,
course.upcoming? ? 0 : 1,
course.title
]
end
end
def upcoming?
start_date && start_date > Time.now.utc
end
into a scope, as self.rank(courses) returns an array rather than an activerecord scope.
current progress (will update til complete):
scope :ranked, -> { order(:list_order) }
possible resources:
Conditional order for nested model based on field
SQL Conditional Order By
scope :just_what_i_want, lambda { |course_id| where(:course_id => course_id) }
scope :ranked, lambda { |list_order| order(list_order ? list_order : Float::INFINITY).order('start_date DESC').order(:title) }
use like:
Courses.just_what_i_want([1,2,3,4,5,6]).ranked('course_id')
I think this should work?

Default scope ignoring dynamic value in condition

In my Activity model, I have a default scope:
default_scope where(:subject_id => Log.get_subject_id)
Problem is in Log.get_subject_id, default value is 0. Here is my Log model:
##subject_id = 0
def self.set_subject_id(val)
##subject_id = val
end
def self.get_subject_id
##subject_id
end
When I change value of ##subject_id via Log.set_subject_id(10) in controller and then I try Activity.all, it always give me bad result. SQL:
SELECT "activities".* FROM "activities" WHERE "activities"."subject_id" = 0
Where is a problem? Thanks!
With that form of default_scope, your Log.get_subject_id call will be evaluated when the class is parsed so you're really saying something like this:
default_scope where(:subject_id => 0)
However, you can use a block with default_scope to delay the evaluation of the scope until you try to use it and, presumably, Log.get_subject_id will have a useful value:
default_scope { where(:subject_id => Log.get_subject_id) }
It is happening because of the scope caching, try wraping it with lambda:
default_scope lambda { { :conditions => { :subject_id => Log.get_subject_id } } }

NHibernate 3.1 ignoring computed column formula

I have a class with two computed columns. The formulas are select statements that grab counts from other tables, like so:
private const string VOTES_FORMULA = "(select count(v.id) from Votes v where v.BusinessID = Id)";
private const string SURVEY_FORMULA = "(select cast((case when exists (select * from surveys s where s.businessid = Id) then 1 else 0 end) as bit))";
// in my bootstrap code...
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update();
});
This was all working fine with Fluent NHibernate 1.1 (using NHibernate 2.1), but I just upgraded to 1.2 (using NH 3.1) and it appears that Fluent NHibernate is ignoring the formulas. I'm getting an "invalid column name" exception for the two fields HasTakenSurvey and Votes because its' trying to query the columns directly rather than executing the formulas as directed. An example query:
exec sp_executesql N'select TOP (#p0) business0_.Id as Id0_, business0_.UserPassword as UserPass2_0_, business0_.HasTakenSurvey as HasTaken3_0_, business0_.Votes as Votes0_, business0_.Origin as Origin0_, business0_.SecurityToken as Security6_0_, business0_.BusinessName as Business7_0_, business0_.BusinessType as Business8_0_, business0_.BusinessImageUrl as Business9_0_, business0_.BusinessDescription as Busines10_0_, business0_.EmployeeCount as Employe11_0_, business0_.OwnerFirstName as OwnerFi12_0_, business0_.OwnerLastName as OwnerLa13_0_, business0_.UserPosition as UserPos14_0_, business0_.BusinessAddress1 as Busines15_0_, business0_.BusinessAddress2 as Busines16_0_, business0_.BusinessCity as Busines17_0_, business0_.BusinessState as Busines18_0_, business0_.BusinessPostal as Busines19_0_, business0_.BusinessCountry as Busines20_0_, business0_.UserBusinessPhone as UserBus21_0_, business0_.UserMobilePhone as UserMob22_0_, business0_.UserEmailAddress as UserEma23_0_, business0_.UserIpAddress as UserIpA24_0_, business0_.OptInReminders as OptInRe25_0_, business0_.OptInOffers as OptInOf26_0_, business0_.OptInSms as OptInSms0_, business0_.Created as Created0_, business0_.Modified as Modified0_ from dbo.Businesses business0_ order by business0_.BusinessName asc',N'#p0 int',#p0=25
Did the implementation change? What am I doing wrong?
As noted in the comments ConventionBuilder.Property.Always(x => x.Column(x.Property.Name)) was adding the column to all properties (and overriding the formula).
Adding .Columns.Clear() to the mapping should remove the column, so:
mappings.Override<Business>(map =>
{
map.IgnoreProperty(x => x.IsNewRecord);
map.IgnoreProperty(x => x.IdString);
map.Map(x => x.UserPassword).CustomType<EncryptedStringType>();
map.Map(x => x.HasTakenSurvey).Formula(SURVEY_FORMULA).Not.Insert().Not.Update().Columns.Clear();
map.Map(x => x.Votes).Formula(VOTES_FORMULA).Not.Insert().Not.Update().Columns.Clear();
});
Columns.Clear() solution provided by #david duffet also didn't work for me. The getter of Formula is private and so you can't filter on the Formula property (you get method-group cannot be converted to value or something like that). NH3.3, FNH 1.3.
My solution - create a custom Attribute in my Model project - IsNHibernateFormulaPropertyAttribute, apply it to my formula properties, and then check for that attribute in my naming convention logic using reflection:
private bool IsFormula(IPropertyInstance instance)
{
var propInfo = instance.Property.DeclaringType.GetProperty(instance.Property.Name);
if (propInfo != null)
{
return Attribute.IsDefined(propInfo, typeof(IsNHibernateFormulaPropertyAttribute));
}
return false;
}
public void Apply(IPropertyInstance instance)
{
if (!IsFormula(instance))
{
instance.Column(Convert(instance.Property.Name));
}
}