Comparing records to find improvements - ruby-on-rails

I have 2 Rails models Client and HealthProfile:
class Client < ApplicationRecord
has_many :health_profiles
end
class HealthProfile < ApplicationRecord
belongs_to :client, optional: true
scope :start, -> { where(status: 'start' ) }
scope :end, -> { where(status: 'end' ) }
end
HealthProfile contains information such as :panic_attacks (there are 25 other conditions) and I want to see what percentage of clients show improvement. To show improvement they would have panic_attacks: true in their :start health_profile and panic_attacks: false in their :end health_profile. The issue is that :panic_attacks defaults to false.
Is there a query I can perform to capture all clients that responded true to :panic_attacks in their :start and false to :panic_attacks in their :end? Ideally this solution will work for all 25 conditions.

What you want to do instead is to create a a many to many association and query it:
class HealthProfile < ApplicationRecord
belongs_to :client, optional: true
has_many :panic_attacks
end
class PanicAttack
belongs_to :health_profile
end
This would let you for example count the number of attacks by using grouping:
profile = HealthProfile.find(1)
# this is postgres specific - use the group_date gem if you need something polyglot
per_month = profile.panic_attacks
.group("date_trunc('month', panic_attacks.started_at)")
.count
And you can also use other sorts of aggregate functions like averages and means to get meaningful stats. For example an imrovement could be a reduction in the count or average length.
Boolean columns are a scurge.

Related

How to use Active Record counter cache with the all scope

I have an activerecord class method scope that returns all when the scope should remain unchanged. However I would expect it to use the counter cache when chaining size to the all scope. Here is an example:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
def self.filtered(only_approved:)
if only_approved
where(approved: true)
else
all
end
end
end
# This does not use the counter cache but should since the scope is unchanged
Post.first.comments.filtered(only_approved: false).size
So it looks like Post.comments.size triggers the counter cache while Post.comments.all.size does not. Is there a way around this?
This happens because of how the counter_cache works. It needs 2 things:
Add the counter_cache: true to the belonging model (Comment)
Add a column comments_count to the having model (Post)
The column added to the Post model gets updated everytime you create or destroy a model so it will count all existing records on the table. This is the reason why it won't work on a scope (a scope might be useful to filter the resulting records, but the actual column comments_count is still counting the whole table).
As a workaround I'd suggest you to take a look at and see if it can be used for your usecase https://github.com/magnusvk/counter_culture.
From their own repo:
class Product < ActiveRecord::Base
belongs_to :category
scope :awesomes, ->{ where "products.product_type = ?", 'awesome' }
scope :suckys, ->{ where "products.product_type = ?", 'sucky' }
counter_culture :category,
column_name: proc {|model| "#{model.product_type}_count" },
column_names: -> { {
Product.awesomes => :awesome_count,
Product.suckys => :sucky_count
} }
end
The only way I found to deal with this is to pass the scope to the class method and return it if no additional scope is to be added. It's not as clean but it works. Here is the updated code:
class Post < ApplicationRecord
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :post, counter_cache: true
def self.filtered(scope:, only_approved:)
if only_approved
scope.where(approved: true)
else
scope
end
end
end
# This works with counter cache if the scope is returned as is
Comment.filtered(scope: Post.first.comments, only_approved: false).size

Order by field in related model with ActiveRecord condition

I am trying to order by a field in a related model in Rails. All of the solutions I have researched have not addressed if the related model is filtered by another parameter?
Item model
class Item < ActiveRecord::Base
has_many :priorities
Related Model:
class Priority < ActiveRecord::Base
belongs_to :item
validates :item_id, presence: true
validates :company_id, presence: true
validates :position, presence: true
end
I am retrieving Items using a where clause:
#items = Item.where('company_id = ? and approved = ?', #company.id, true).all
I need to order by the 'Position' column in the related table. The trouble has been that in the Priority model, an item could be listed for multiple companies. So the positions are dependent on which company_id they have. When I display the items, it is for one company, ordered by position within the company. What is the proper way to accomplish this? Any help is appreciated.
PS - I am aware of acts_as_list however found it did not quite suit my setup here, so I am manually handling saving the sorting while still using jquery ui sortable.
You could use the includes method to include the build association then order by it. You just make sure you disambiguate the field you are ordering on and there are some things you should read up on here on eager loading. So it could be something like:
#items = Item.includes(:priorities).where('company_id = ? and approved = ?', #company.id, true).order("priorities.position ASC")
class Item < ActiveRecord::Base
has_many :priorities
belongs_to :company
def self.approved
where(approved: true)
end
end
class Priority < ActiveRecord::Base
belongs_to :item
end
class Company < ActiveRecord::Base
has_many :items
end
#company = Company.find(params[:company_id])
#items = #company.items.joins(:priorities).approved.order(priorities: :position)
If I've understood your question, that's how I'd do it. It doesn't really need much explanation but lemme know if you're not sure.
If you wanted to push more of it into the model, if it's a common requirement, you could scope the order:
class Item < ActiveRecord::Base
has_many :priorities
belongs_to :company
def self.approved
where(approved: true)
end
def self.order_by_priority_position
joins(:priorities).order(priorities: :position)
end
end
and just use: #company.items.approved.order_by_priority_position

Force where close on every query of a model plus nesting

I first had in mind to use the default_scope to force a condition on every requests but it doesn't seems to work.
For example, if I have Product.include(:prices) it will no use the default scope. I'm not sure if its the correct behavior.
My default_scope for prices is something like that:
default_scope where(type: "normal")
I'm working on a "crm" ported to rails and the only type of price that interest us is the "normal" one. I could edit the code everywhere but it would be much simpler to "enforce" a condition whenever the price needs to be queried from the database. And for that same reason, using named_scope isn't an alternative either for now since it might require a lot of refactor.
Some more information as it "should" work but doesn't...
class Ts::Price < ActiveRecord::Base
acts_as_terrasoft
default_scope where(PriceKindID: "{43D5117A-D52D-4E72-B33B-7D3158524BF1}")
..
other scopes
..
end
The actual call
products = Product.includes(:supplier, :prices, :user)
... some more where unrelated to prices
products = products.find(:all, {
conditions: {
:vendors_Products => {
AccountTypeID: type
}
},
limit: #count,
offset: #offset * #count
})
The Products
class Ts::Product < ActiveRecord::Base
acts_as_terrasoft
self.table_name = "Products"
self.primary_key = "ID"
has_many :prices, class_name: "::Ts::Price", foreign_key: "ProductID", :dependent => :destroy
end
Edit
Rails 3.2.8

Rails 3: Filtering results by counting children

I'm working on an approval system where an example would be filtered out if it had more negative approvals than positive.
class Set < ActiveRecord::Base
has_many :examples
end
class Example < ActiveRecord::Base
has_many :approvals, as: :approvable
end
class Approval < ActiveRecord::Base
belongs_to :approvable, polymorphic: true
belongs_to :example
attr_accessible :choice #boolean
end
I can get all approvals belonging to an Example by calling Example.approvals.
What i'm wondering about is if it's possible to create a method on Example model that i could then use a la Example.approved that would return true or false with a single query depending on if the example has more Approvals with true or false. Ultimately i would want to call only the examples of a set that have been approved by calling Set.approved_examples.
Example.approved i can kind of do with
class Example < ActiveRecord::Base
...
def approved
if approvals.where(choice: true).count > approvals.where(choice: false).count
return true
end
false
end
end
although that takes two queries.
Set.approved_examples would look like the SQL View approved_examples in here but i'm not sure where to start if i wanted a scope like this on the Set model.
Okay, based on your comment I think I misunderstood your question but maybe you coudld try something like this:
Example.all.select { |example| example.approved == true }
That would get you an array of all approved examples.
Set.first.examples.all.select { |example| example.approved == true }
That would get you an array of all approved examples that belong to the first set record.
I think there's probably a more optimal way to do this, but this does work.

Multiple counter_cache in Rails model

I'm learning Rails, and got into a little problem. I'm writing dead simple app with lists of tasks, so models look something like that:
class List < ActiveRecord::Base
has_many :tasks
has_many :undone_tasks, :class_name => 'Task',
:foreign_key => 'task_id',
:conditions => 'done = false'
# ... some validations
end
Table for List model has columns tasks_counter and undone_tasks_counter.
class Task < ActiveRecord::Base
belongs_to :list, :counter_cache => true
# .. some validations
end
With such code there is attr_readonly :tasks_counter for List instances but I would like to have a counter for undone tasks as well. Is there any way of having multiple counter cached automagically by Rails.
So far, I've managed to create TasksObserver that increments or decrements Task#undone_tasks_counter, but maybe there is a simpler way.
Have you tried it with a custom-counter-cache column?
The doc here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
It suggests that you can pass a column-name to the counter_cache option, which you may well be able to call twice eg
belongs_to :list, :counter_cache => true # will setup tasks_count
belongs_to :list, :counter_cache => :undone_tasks_count
Note: not actually tested.
ez way.
1) first counter - will do automatically
2) Manually "correct"
AnotherModelHere
belongs_to :user, counter_cache: :first_friends_count
after_create :provide_correct_create_counter_2
after_destroy :provide_correct_destroy_counter_2
def provide_correct_create_counter_2
User.increment_counter(:second_friends_count, another_user.id)
end
def provide_correct_destroy_counter_2
User.decrement_counter(:second_friends_count, another_user.id)
end
Most probably you will need counter_culture gem, as it can handle counters with custom conditions and will update counter value not only on create and destroy, but for updates too:
class CreateContainers < ActiveRecord::Migration[5.0]
create_table :containers, comment: 'Our awesome containers' do |t|
t.integer :items_count, default: 0, null: false, comment: 'Caching counter for total items'
t.integer :loaded_items_count, default: 0, null: false, comment: 'Caching counter for loaded items'
end
end
class Container < ApplicationRecord
has_many :items, inverse_of: :container
has_many :loaded_items, -> { where.not(loaded_at: nil) },
class_name: 'Item',
counter_cache: :loaded_items_count
# Notice that you can specify custom counter cache column name
# in has_many definition and AR will use it!
end
class Item < ApplicationRecord
belongs_to :container, inverse_of: :items, counter_cache: true
counter_culture :container, column_name: proc { |model| model.loaded_at.present? ? 'loaded_items_count' : nil }
# But this column value will be handled by counter_culture gem
end
I'm not aware of any "automagical" method for this. Observers seems good for this, but I personally prefer using callbacks in model (before_save, after_save).

Resources