Paginate with order defined by joined model in Rails - ruby-on-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])

Related

ActiveRecords returning model object from a join table

I have the following DB for a simple flash cards example i'm building:
create_table "card_associations", :force => true do |t|
t.integer "card_id"
t.integer "deck_id"
end
create_table "cards", :force => true do |t|
t.string "question"
t.string "answer"
end
create_table "decks", :force => true do |t|
t.string "name"
t.string "description"
end
I've setup the has_many through relationships in all my models.
Now I want to be able to return a list of all cards from the join table, given the deck id.
If I run the query:
CardAssociation.find_by_deck_id(3).card
It retruns the first card with the deck_id of 3. But when I try.
CardAssociation.find_all_by_deck_id(3).card
I get the error
NoMethodError: undefined method `card' for #
Can someone help me with this? I feel like i'm making a very simple mistake.
Thanks for the help
The find_all_* methods always return an Array (which could be empty)!
CardAssociation.find_all_by_deck_id(3) # => Array of results
CardAssociation.find_all_by_deck_id(3).first # => first result of the Array or nil if no result
I advise you to first read the Ruby on Rails Style Guide, and then use the Rails3 way of finding object with ActiveRecord:
CardAssociation.where(:deck_id => 3) # => Array of results
CardAssociation.where(:deck_id => 3).first # => first result of the Array if exists
In your case, a scope can be set up on the Card model:
You said: "Now I want to be able to return a list of all cards from the join table, given the deck id"
class Card < ActiveRecord::Base
scope :for_deck, lambda { |deck| joins(:card_associations).where('card_associations.deck_id = ?', deck.try(:id) || deck) }
end
This scope can be used like following:
Card.for_deck(deck) # returns an Array of Card objects matching the deck.id
As defined in the scope, the parameter of Card.for_deck(deck) can be a deck object or a deck_id (type Integer)
Hope this helped!

Rails + model.rb issues -- rails console error message -- "Object doesn't support #inspect"

EDIT3:
To wit, DO NOT put migration code in your model.rb files!!!
EDIT2: THE QUESTION (?) :
does ANY migration code belong in a model.rb file?
EDIT: Just mentioning extra (system/config/etc) information that I need to share in order to get a good answer to this question from someone (even if it's not you) would be greatly appreciated. (1-ups for good tips on stack overflow optimization strategies)
First of all, here is the command prompt activity:
C:\Users\davo\Desktop\RailsProjects\simple_cms>rails c
Loading development environment (Rails 3.2.3)
irb(main):001:0> subject = Subject.find(1)
←[1m←[36mSubject Load (1.0ms)←[0m ←[1mSELECT `subjects`.* FROM `subjects` WHERE `subjects`.`id` = 1
LIMIT 1←[0m
=> #<Subject id: 1, name: "Initial Subject", position: 1, visible: true, created_at:"2012-05-18 01:00:26", updated_at: "2012-05-18 01:11:21">
irb(main):002:0> subject.pages
(Object doesn't support #inspect)
The basic schema is that we have two models here, page.rb and subject.rb. Subject is the parent of Page, as you will see. Here are the two models.
Guide to viewing this code: I think all that is relevant to this problem in these two models are the has_many and belongs_to tags. And I admit, I feel like there should be some foreign keys here. Should there be foreign keys here? Or is that wrong too?
subject.rb
class Subject < ActiveRecord::Base
# attr_accessible :title, :body
has_many :pages
scope :visible, where(:visible => true)
scope :invisible, where(:visible => false)
scope :search, lambda {|query| where(["name LIKE ?", "%#{query}%"])}
end
page.rb
class Page < ActiveRecord::Base
has_many :sections
belongs_to :subject
# attr_accessible :title, :body
create_table "Pages" do |t|
t.string "name"
t.string "permalink"
t.integer "position"
t.boolean "visible?"
end
end
I'm really new at this, so please forgive me if I didn't give you some piece of information that you need. Please let let know what extra information you need, I'm not sure where the error is coming from but I know this is a model (M....VC) issue. 95% on that one.
You have a migration in your model.
create_table "Pages" do |t|
t.string "name"
t.string "permalink"
t.integer "position"
t.boolean "visible?"
end
Should be in ./db/migrate/{timestamp}_create_pages.rb. This file was generated for you if you did rails g model page
You also need a subject_id column to store the relation to subject
class CreatePages < ActiveRecord::Migration
def change
create_table :pages do |t|
t.integer :subject_id
t.string :name
t.string :permalink
t.integer :position
t.boolean :visible?
t.timestamps
end
end
end

RoR Search based on many to many association such as tags

I have built a RoR database with several different models. Each "record" has many taggings and also is put into a category by a "cat_id." I have built all the models, but I need help with filtering. Right now I have the ability to select a category from a tree view with nodes and it shows all the records in that category and children categories. I can also do a simple text search with a search function (LIKE operator in SQL). I want to add an additional filter of tags to be able to select a category and a tag and show the results.
Here's what I've got so far:
RECORD MODEL
class AuctionRecord < ActiveRecord::Base
#comments
has_many :comments, :dependent => :destroy
#tags
has_many :auction_records_tags
has_many :tags, :through => :auction_records_tags
TAG MODEL
class Tag < ActiveRecord::Base
attr_accessible :name
has_many :auction_records_tags
has_many :auction_records, :through => :auction_records_tags
end
AUCTIONS_RECORDS_TAGS MODEL
class AuctionRecordsTag < ActiveRecord::Base
attr_accessible :auction_record_id, :tag_id
belongs_to :tag
belongs_to :auction_record
end
As you can see the records and tags have a many to many relationship so it is possible to select many tags and be returned with many auction records.
RECORD CONTROLLER
#auction_records = AuctionRecord.filter(session[:search], #cat_sql).order(sort_column + " " + sort_direction).paginate(:per_page => 20, :page => session[:page] )
FILTER FUNCTION
#this is the main search engine
def self.filter(search, cat_sql)
search_sql = ""
if search != ""
search_sql = "title LIKE '%#{search}%' OR itemnumber LIKE '%#{search}%' OR buyer LIKE '%#{search}%' OR seller LIKE '%#{search}%'"
end
if cat_sql != "" && search == ""
search_sql = cat_sql
end
if cat_sql != "" && search != ""
search_sql = search_sql + " AND " + cat_sql
end
if search_sql !=""
where(search_sql)
else
scoped
end
end
I generate the category sql statement manually by for looping through all the nodes and children nodes. As you can see I do almost everything "manually" (not the best way). The fact that certain filters may also not be defined was troublesome (for example a category selection, but no search term). I think all three filters can be accomplished in one line of code, but I'm not sure how it's done. Perhaps something like tags.auction_records? If I need to post the schema of the tables, I can do that too, but for now all the categorization and taggings are done with integer/id relationships.
Thanks in advance for any help / comments
Best regards
Added Info #1
NODE MODEL (used for categorization/categories)
class Node < ActiveRecord::Base
has_many :nodes
belongs_to :node
end
SCHEMA
create_table "auction_records", :force => true do |t|
t.string "title"
t.string "categorization"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "auction_records_tags", :force => true do |t|
t.integer "auction_record_id"
t.integer "tag_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "nodes", :force => true do |t|
t.integer "node_id"
t.string "text"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "tags", :force => true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
end
For "auction_records", the field categorization contains the node_id integer to point to which node it belongs to on the tree view. If you click on a single node (category), many auction_records appear. The auction_records_tags also creates the association between auction_records and tags. I have found something that works for only one tag_id, but I will have to work on something for multiple tags.
Right now I have this code and it is returning the error "Association named "nodes" was not found"
search_term = session[:search]
term = "%#{search_term}%"
#auction_records = AuctionRecord.scoped
#auction_records = #auction_records.where('title LIKE ?', term).where('description LIKE ?',term) if search_term
#auction_records = #auction_records.joins(:nodes).where('node.node_id IN ?', session[:cat_id]) if session[:cat_id]
#auction_records = #auction_records.joins(:tags).where('tags.id = ?', session[:tag_ids]) if session[:tag_ids]
#auction_records = #auction_records.order(sort_column + " " + sort_direction).paginate(:per_page => 20, :page => session[:page] )
Remember that in Rails 3 you can do chained queries that are less error prone than concatenation, also is better to use wildcards to prevent SQL injection. You can refactor the code to look similar to this:
def self.filter(search_term, categories, tags)
term = "%#{search_term}%"
auction_records = AuctionRecord.scoped
auction_records = auction_records.where('title LIKE ?', term).where('other_field LIKE ?',term) if search_term
auction_records = auction_records.joins(:categories).where('categories.name IN ?', categories) if categories
auction_records = auction_records.joins(:tags).where('tags.name IN ?' tags) if tags
end

How do you seed models with HABTM relationships to other seeded models

I'm working on my first Rails(3) App, and looking to seed a bunch of data.
The issue I'm having is that I want to seed some models that have a has_and_belongs_to_many relationship with other models I've just seeded. I'm doing what seems right, but I'm not getting the results I'm expecting.
I have an Asana model (simplified):
class Asana < ActiveRecord::Base
has_and_belongs_to_many :therapeutic_foci
end
and the TherapeuticFocus model:
class TherapeuticFocus < ActiveRecord::Base
has_and_belongs_to_many :asanas
end
In my db/seeds.rb, I create some TherapeuticFoci:
tf = TherapeuticFocus.create([
{:name => 'Anxiety'},
{:name => 'Asthma'},
{:name => 'Fatigue'},
{:name => 'Flat Feet'},
{:name => 'Headache'},
{:name => 'High Blood Pressure'},
{:name => 'Stress'} ])
Then create an Asana:
asanaCreate = Asana.create!([
{ :english_name => 'Mountain Pose',
:traditional_name => 'Tadasana',
:pronunciation => 'TadaSANA',
:deck_set => 'Basic',
:type => 'Standing',
:therapeutic_foci => TherapeuticFocus.where("name in ('Stress','Flat Feet')")}
])
The result is that the TherapeuticFocus models are created, the Asana is created, but it doesn't create the relationships to the TherapeuticFocus models. The resulting array is empty.
If I run
TherapeuticFocus.where("name in ('Stress','Flat Feet')")
in the rails console, I get the expected two records:
irb(main):010:0> TherapeuticFocus.where("name in ('Stress','Flat Feet')")
=> [#<TherapeuticFocus id: 6, name: "Flat Feet", description: nil, created_at: "2010-10-11 01:48:02", updated_at: "2010-10-11 01:48:02">,
#<TherapeuticFocus id: 19, name: "Stress", description: nil, created_at: "2010-10-11 01:48:02", updated_at: "2010-10-11 01:48:02">]
So, how does one do this?
Or, is there a better way to do this?
Thanks!
POST ANSWER:
I had already added the inflection:
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'focus', 'foci'
end
My migration for the join tables looks like:
create_table :asanas_therapeutic_foci, :id => false do |t|
t.references :asana, :therapeutic_focus
end
I'll try changing this to t.belongs_to instead of t.references and see if that works.
Did you register the pluralization for “focus”? It is not defined by default, so you will need to define it (usually in config/initializers/inflections.rb):
ActiveSupport::Inflector.inflections do |inflect|
inflect.irregular 'focus', 'foci'
end
You also need to make sure your migration has defined the correct join table for the HABTM association. Here is the pertinent part of the “up” migration that I used:
create_table :asanas do |t|
t.string :english_name
t.string :traditional_name
t.string :pronunciation
t.string :deck_set
t.string :type
end
create_table :therapeutic_foci do |t|
t.string :name
end
create_table :asanas_therapeutic_foci, :id => false do |t|
t.belongs_to :asana
t.belongs_to :therapeutic_focus
end
I used the models as you quoted.
With those bits in place, I was able to load your seed definitions.

DB fields not showing up in association custom queries?

I have a notification list that the user can select different show options for different kinds of notifications along with how many results are returned. I'm keeping them in the user model because I want the custom sort to stay with the user between sessions. Here is the association in my user model:
has_many :notifications,
:class_name => "Notification",
:foreign_key => "user_id",
:conditions => ["won = ? and lost = ? and paid = ?", self.prefs_won, self.prefs_lost, self.prefs_paid],
:limit => self.prefs_results.to_s
But when I use the above code, Rails throws me an "unknown method" error for self.prefs_won. It is definitely a field in my database and set as a boolean value, but Rails can't find it... what's the problem?
EDIT:
Here's the migration:
t.boolean :prefs_won, :default => true
t.boolean :prefs_lost, :default => true
t.boolean :prefs_paid, :default => true
t.integer :prefs_results, :default => 10
ActiveRecord accessors for boolean attributes have a question mark at the end of the attribute name. Try self.prefs_won? instead.

Resources