Tables:
User
Project has_many Results
Project has_many Data through ProjectData
Results belongs_to data, project
In the Result table I have a column :position of type int.
So I would like to get all the results with a level < 50, actually the value of count.
I am thinking in adding in the Result class
def get_top_level current_user
tsum = []
Project.where(user_id: current_user).each do |project|
tsum << project.results.where("level <= ?", 50).count
end
return sum(tsum)
end
This will work, but I feel that there should be a easy and prettier way of doing this.
And is it ok to user the class name in a view and pass different values for example:
<%=Results.get_top_level(current_user)%>
Or
<%=#results.get_top_level(current_user)%>
If none of those are a good practice, can you help me with a alternative solution for this.
Thank you.
I would create a method on the project model something like this.
def get_top_level
self.results.select{ |result| result.level <= 50 }
end
On the user model. What's the relationship here, does a user have many projects? Or just one project.
def get_top_level
self.top_level_projects.inject(:+)
end
def top_level_projects
self.projects.map(&:get_top_level)
end
Now when you call current_user.get_top_level
This will find the top_level_projects, map the associated results and add them all together.
Related
In my rails controller I'd like to retrieve only the Projects where the current user's id is in a param array.
This is what I currently have:
class MyProjectsController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
#projects = Project.where(team_member: [#user])
end
private
def project_params
params.require(:project).permit(:name, :team_member => [])
end
end
My expectation was that this would return projects if #user was included in the array...
It actually only returns projects if #user is the only value in the array. If the array contains multiple values nothing is returned.
I'm fairly new to rails and feel like I'm missing something really obvious here.
Thanks in advance!
Update:
So I was adding Users to a Project via a column on the Projects table called :team_member which accepts an array of user IDs.
I did get this to work by doing a query like this:
#projects = Project.where(":team_member = ANY(team_member)", team_member:
[current_user.id])
This returns only projects where current_user.id exists in the :team_member array.
BUT I realize that I really need to perhaps do this with a join table on User and Projects and add users to projects that way. Thanks for the comments...they all helped me think through this.
We could help better you show us the Project model. Specifically the association between Project and team_member.
If team_member is a has_many association, you could join it and filter by their ids, like:
Project.joins(:team_members).where(team_members: { id: YOUR_ARRAY_OF_IDS })
But maybe team_member is an array, or some sort of data serialized in Project model, for that we could search array elements using ANY or ALL, using postgresql, I can't tell much about other DBMS.
Please tell more about this model and it's associations
i would suggest you to use either has_any_belongs_to_many relationship , or has_many :through for essay querying database
https://guides.rubyonrails.org/association_basics.html#the-has-many-through-association
https://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
Try this:
#projects = Project.all.where(team_member: current_user)
I was reading this article: https://www.salsify.com/blog/engineering/eager-loading-calculations-database-views-in-rails
I really like the concept of creating a model that is actually a query to another model, in order to create some calculations.
What I'd like to do is currently very simple, for each Tag, I like to get the TagVisits and sum the visits attribute.
The code looks like this:
class TagVisitSummary < ApplicationRecord
belongs_to :tag
default_scope { set_from_clause }
def ==(other)
self.class == other.class && attributes == other.attributes
end
alias :eql? :==
def hash
attributes.hash
end
private
def self.set_from_clause
query = TagVisit.select(:tag_id).group(:tag_id).select('SUM(visits) as total_visits').all
from("(#{query.to_sql}) AS #{table_name}")
end
def self.columns
cols = [ActiveRecord::ConnectionAdapters::Column.new('tag_id', nil, :integer)]
cols << ActiveRecord::ConnectionAdapters::Column.new("total_visits", nil, :integer)
cols
end
end
When I paste the SQL generated in set_from_clause directly in my Postico, I see exactly the result I'm looking for. Unfortuntaly running TagVisitSumamry.all yields no results (and also no error). So, I'm having some doubts whether the self.columns method is correct. The example is in Rails 4, I'm using 5. Has there been some changes that require me to change this approach?
Thanks
Have you tried, scenic? https://github.com/thoughtbot/scenic
It helps with creating database views and materialized views and you can use them as ActiveRecord models.
I want to speed up my query, I haven't done any index on my tables.
But I have no idea where should I put the index in the right place.
Controller
def search
#rooms = Room.includes(:hotel, :skus).where(id: available_rooms_ids)
end
JSON builder
My JSON builder is kind of complex.
Children need to fetch its parent data.
Patent also needs to fetch its children data as well.
Their relationship chain looks like
Hotel HAS_MANY Room
Room HAS_MANY RoomSku
RoomSku HAS_MANY STOCK
CODE
json.array!(#rooms) do |item|
json.merge! item.attributes
json.hotel item.hotel
json.room_skus do
json.array! item.skus do |sku|
next if (sku.date < #check_in_time.to_date or sku.date > #check_out_time.to_date )
json.merge! sku.attributes.merge({stock:sku.stock})
end
end
json.img_src_url item.hotel.images.last.src.url
end
end
You could use something like pg_idx_advisor to find out what indexes your database needs and then write a migration that will create such indexes.
Let's say I have a relation in Rails to a table that uses STI like:
class Vehicle < ActiveRecord::Base; end
class Car < Vehicle; end
class Truck < Vehicle; end
class Person < ActiveRecord::Base
has_many :cars
has_many :trucks
has_many :vehicles
end
... and I want to load a Person and all of its cars and trucks in one query. This doesn't work:
# Generates three queries
p = Person.includes([:cars, trucks]).first
... and this is close, but no luck here:
# Preloads vehicles in one query
p = Person.includes(:vehicles).first
# and this has the correct class (Car or Truck)
p.vehicles.first
# but this still runs another query
p.cars
I could do something like this in person.rb:
def cars
vehicles.find_all { |v| v.is_a? Car }
end
but then Person#cars isn't a collection proxy anymore, and I like collection proxies.
Is there an elegant solution to this?
EDIT: Adding this to Person gives me the items I want in arrays with one query; it's really pretty close to what I want:
def vehicle_hash
#vehicle_hash ||= vehicles.group_by {|v|
v.type.tableize
}
end
%w(cars trucks).each do |assoc|
define_method "#{assoc}_from_hash".to_sym do
vehicle_hash[assoc] || []
end
end
and now I can do Person.first.cars_from_hash (or find a better name for my non-synthetic use case).
When you use includes, it stores those loaded records in the association_cache, which you can look at in the console. When you do p = Person.includes(:vehicles), it stores those records as an association under the key :vehicles. It uses whatever key you pass it in the includes.
So then when you call p.cars, it notices that it doesn't have a :cars key in the association_cache and has to go look them up. It doesn't realize that Cars are mixed into the :vehicles key.
To be able to access cached cars as either through p.vehicles OR p.cars would require caching them under both of those keys.
And what it stores is not just a simple array—it's a Relation. So you can't just manually store records in the Hash.
Of the solutions you proposed, I think including each key is probably the simplest—code-wise. Person.includes(:cars, :trucks) 3 SQL statements aren't so bad if you're only doing it once per request.
If performance is an issue, I think the simplest solution would be a lot like what you suggested. I would probably write a new method find_all_cars instead of overwriting the relation method.
Although, I would probably overwrite vehicles and allow it to take a type argument:
def vehicles(sti_type=nil)
return super unless sti_type
super.find_all { |v| v.type == sti_type }
end
EDIT
You can get vehicles cached by Rails, so you probably can just rely on that. Your define_methods could also do:
%w(cars trucks).each do |assoc|
define_method "preloaded_#{assoc}" do
klass = self.class.reflect_on_all_associations.detect { |assn| assn.name.to_s == assoc }.klass
vehicles.select { |a| a.is_a? klass }
end
end
Even if you don't use includes, the first time you call it, it will cache the association—because you're selecting, not whereing. You still won't get a Relation back, of course.
It's not really that pretty, but I like that it's contained to one method that doesn't depend on any other ones.
Lets say Users have BankAccounts which have a balance value.
So, how do I generate an array that Users and the total value of all their BankAccounts' values added together?
I'm not sure if this is quite what you want, but you can do something like:
ActiveRecord::Base.connection.select_all("select user_id, sum(balance) from accounts group by user_id;")
This will give you an array of user_ids and balances from the accounts table. The advantage of doing it this way is that it comes down to only one SQL query.
You'll want to do something like this. I don't believe it's possible to use #sum via an association.
class User
def net_worth
BankAccount.sum(:balance, :conditions => ["user_id = ?", self.id])
end
end
Edit
I see a reference to a #sum in AssociationCollection, so try this:
class User
def net_worth
self.bank_accounts.sum(:balance)
end
end
(I haven't tested this code)
First you need to find the users you want so I'll just assume you want all users.
#users = User.all
Then you need to take the array and collect it with only the elements you want.
#users.collect! {|u| [u.name, u.bank_account_total_value]}
For this kinda attribute I would set it in the model assuming you have has_many :transactions as an association
Class User
has_many :transactions
def bank_account_total_value
total = 0
self.transactions.each do |t|
total += t.amount
end
end
end