Rails, Sphinx (thinking_sphinx) and sorting from associations - ruby-on-rails

I have 3 models:
Stats (belongs_to :item)
t.integer :skin_id
t.integer :item_id
t.integer :rating
Item (has_many :stats)
and
Skin (has_many :stats)
Using thinking_sphinx i want to create a separate index, for items, sorted by :rating for particular :skin_id
So, i'm trying to:
define_index 'sort_by_rate' do
indexes stats(:rating), :as => :ratings, :sortable => true
end
But this, will generate an index for all :skin_id (in the Stat model), not for particular one.
In other words, i need to gather all items, sorted by Stat.rating, with Stat.skin_id == 1 (for example).
Here is the example of SQL:
"SELECT `stats`.* FROM `stats` INNER JOIN `items` ON `items`.`id` = `stats`.`item_id` WHERE `stats`.`skin_id` = 1 ORDER BY rating DESC"
ANy solutions is very appreciated!

Perhaps you should have in your define_index block:
has :skin_id
and then, when searching, filter by that. Something like this:
Item.search(:with => {:skin_id => skin.id}, :order_by => 'ratings ASC')

Related

How to map one user to another (from same table)

I'd like to know how to solve this problem in my model/migrations, with correct referential integrity/uniqueness constraints.
I have a user table with two types of user: support_worker and service_user (like teacher and pupil). A support_worker can provide support for many service_users. I used to have separate tables for these respective user types, but for simplicity it makes more sense to have both user types in a single 'user' table (for Devise).
I'll have another table called support_allocation which records the relationship between a support_worker and the service_user(s) they support - this support_allocation has other information stored about it (like a budget; time/money). So this table needs to map one user_id to another user_id. I imagine the table structure will look something like this: SupportAllocation (id, support_worker_id, service_user_id)
So far, my migrations look like this (I've used Devise gem to create the user table so this amends it):
class ChangeUsers < ActiveRecord::Migration[5.2]
def change
change_table :users do |t|
t.string :user_type # support_worker or service_user
t.string :given_name
t.string :family_name
t.string :customer_reference # only for service_users
t.date :date_of_birth # only for service_users
t.string :job_roles # only for support_workers
end
end
class CreateSupportAllocations < ActiveRecord::Migration[5.2]
def change
create_table :support_allocations do |t|
t.boolean :active, default: true
# This next bit is guesswork
t.integer support_worker_id # support_worker's user_id
t.integer service_user_id # service_user's user_id
t.timestamps
end
end
end
Here's where I get confused... I need to create a join, but this will only do it on user_id, whereas the relationship is defined by the two user_id columns (as shown and named above). I'm not sure if this a compound key or if a single foreign key (or two) will suffice.
Here's my migration work-in-progress:
class AddJoins < ActiveRecord::Migration[5.2]
def change
change_table :support_allocations do |t|
t.belongs_to :user, index: true
end
end
end
I'd like to know how to achieve this. For the record, I'm using ActiveAdmin for my app. Thank you for your help.
I don't think you need the AddJoins migration. Add 2 associations in your CreateSupportAllocations model like so:
belongs_to :support_worker, :foreign_key => :support_worker_id, :class_name => User
belongs_to :service_user, :foreign_key => :service_user_id, :class_name => User
In your activeadmin form you can set the collections for the select, for example
(in app/admin/support_allocations.rb)
form do |f|
f.inputs do
# your inputs
f.input :support_worker, :as => :select, :collection => User.where(:user_type => 'support_worker')
f.input :service_user, :as => :select, :collection => User.where(:user_type => 'service_user')
end
f.actions
end
# added after comments
index do
selectable_column
column :support_worker
actions
end
Add a to_s method in you user model like so:
def to_s
"#{self.full_name}"
end
Thanks for all your help. I added the suggested associations to my SupportAllocation model. For the record, I also had to add the following associations to my User model to make the join work fully, in both directions:
has_many :occurances_as_support_worker, :class_name => 'SupportAllocation', :foreign_key => 'support_worker_id'
has_many :occurances_as_service_user, :class_name => 'SupportAllocation', :foreign_key => 'service_user_id'
I used the example given here to work this out.
When accessing attributes specific to a type of user (i.e. using the join over support_worker_id OR service_user_id), on the index page. I use code like this:
column 'Service user', :full_name, :sortable => 'service_users.family_name' do |support_allocation|
#ServiceUser.find(support_allocation.service_user_id).full_name
support_allocation.service_user.full_name
end
column 'Support worker', :full_name, :sortable => 'support_workers.family_name' do |support_allocation|
support_allocation.support_worker.full_name
end

Filter rails record by 2 instances of child record model

I'm trying to filter a list of Products based on 2 tags,
class Product < ActiveRecord::Base
has_many :tags
end
class Tag < ActiveRecord::Base {
:id => :integer,
:created_at => :datetime,
:updated_at => :datetime,
:key => :string
}
How can I format a query statement that allows me to find a product which has 2 tags, one with key 'fragile', and one with key 'perishable'?
Product.joins(:tags).where("tags.key IN (?)", ['fragile', 'perishable']).group('products.id').having('COUNT(tags.id) = ?', 2)

Paginate with order defined by joined model in Rails

I have currently the following problem in my Rails application (Rails 2.3.5):
I want to sort books stored in the application by the author name, then the title of the book.
Book and Author are concrete models, the corresponding tables are Ressources and People. The relevant portion of the schema is (I have stripped down the model a bit):
create_table "people", :force => true do |t|
t.string "sur_name"
t.string "pre_name"
...
t.string "type"
end
create_table "people_ressources", :id => false, :force => true do |t|
t.integer "ressource_id"
t.integer "person_id"
end
create_table "ressources", :force => true do |t|
t.string "type"
t.string "title"
end
To show the list of books, I have used the following paginator:
#books = Book.paginate(
:order => 'title', :per_page => 15, :page => params[:page])
My question now is: How should the paginator be constructed so that the books are ordered not by title, but first by author (== person) sur_name? And if that is not easily reachable, what construct would allow to store books and authors as separate entities, but would allow to get a paginator with the defined order?
Given that you have multiple authors for a book you would need to decide how you determine which is the author whose name should be used to order the list of books.
You could add an association has_one :main_author, :class_name => 'Author' to your Book class defined however you wish (maybe there is a primary author field in people_ressources or maybe you just use the first author available:
has_one :main_author, :through => :author_books, :order => 'sur_name'
Having that has_one association means that you can include that in the pagination and order against the sur_name there:
#books = Book.paginate(:order => "#{Author.table_name}.sur_name",
:per_page => 15,
:page => params[:page])

Order results in sub table

#posts = Category.find(params[:id]).posts
How can I order the results with column from the posts table? For example on the posts.created_at column?
You can do this:
#posts = Category.find(params[:id]).posts.all(:order => "created_at")
not sure if there are better ways of doing it... hope that helps =)
#posts = Category.find(params[:id]).posts.all(:order => "created_at")
You can also add to this other things such as
#posts = Category.find(params[:id]).posts.all(:order => "created_at", :limit => 10)
or
#posts = Category.find(params[:id]).posts.all(:order => "created_at DESC")
Another very simple solution is to simply specify the order on the association itself.
class Post < ActiveRecord::Base
belongs_to :category
end
class Category < ActiveRecord::Base
has_many :posts, :order => "created_at"
end
Any posts fetched via the association will already be sorted. This will let you keep the ordering details in the model itself and the SQL-ish syntax out of the controller.
#posts = Category.find(params[:id]).posts
Will give you back your records in "created_at" order.

Rails join three models and compute the sum of a column

I need some help over here to understand how the model relationship works on rails. Let me explain the scenario.
I have created 3 models.
Properties
Units
Rents
Here is the how relationship mapped for them.
Model #property.rb
class Property < ActiveRecord::Base
has_many :units
has_many :rents, :through=> :unit
end
Model #unit.rb
class Unit < ActiveRecord::Base
belongs_to :property
has_many :rents
end
Model #rent.rb
class Rent < ActiveRecord::Base
belongs_to :unit
end
here is the the schema
create_table "units", :force => true do |t|
t.integer "property_id"
t.string "number"
t.decimal "monthly_rent"
end
create_table "rents", :force => true do |t|
t.integer "unit_id"
t.string "month"
t.string "year"
t.integer "user_id"
end
OK, here is my problem. Let's say I have 2 properties
property A
property B
and
property A has unit A01,A02,A03
property B has unit B01,B02,B03
I need to generate a report which shows the SUM of all the outstanding rents based on the property and month
So here is how it should be looks like. (tabular format)
Property A - December - RENT SUM GOES HERE
Property B - December - RENT SUM GOES HERE
So I got all the properties first. But I really can't figure out a way to merge the properties and units (I guess we don't need the rents model for this part) and print them in the view. Can someone help me to do this. Thanks
def outstanding_by_properties
#properties = Property.find(:all)
#units = Unit.find(:all,:select=>"SUM(monthly_rent) as total,property_id",:conditions=>['property_id IN (?)',#properties])
end
I think something like this will work for you. Hopefully an SQL guru will come along and check my work. I'm assuming your Property model has a "name" field for "Property A," etc.--you should change it to whatever your field is called.
def outstanding_by_properties
Property.all :select => "properties.name, rents.month, SUM(units.monthly_rent) AS rent_sum",
:joins => { :units => :rents },
:group => "properties.id, rents.month, rents.year"
end
This should return an array of Property objects that have the attributes name, month, and rent_sum.
It basically maps to the following SQL query:
SELECT properties.name, rents.month, SUM(units.monthly_rent) AS rent_sum
FROM properties
JOIN units ON properties.id = units.property_id
JOIN rents ON units.id = rents.unit_id
GROUP BY properties.id, rents.month, rents.year
The JOINs connect rows from all three tables and the GROUP BY makes it possible to do a SUM for each unique combination of property and month (we have to include year so that e.g. December 2008 is not grouped together with December 2009).

Resources