ROR Associations group by query - ruby-on-rails

I have the following 3 models and relationship between them:
class Calendar < ActiveRecord::Base
has_many:fiscalcalendars
has_many:voucherdatas ,:through => :fiscalcalendars
end
class Fiscalcalendar < ActiveRecord::Base
belongs_to :calendar
has_many :voucherdatas
end
class Voucherdata < ActiveRecord::Base
has_many :fiscalcalendars
has_many :calendars, :through => :fiscalcalendars
end
fields in calendar : id,monthname
fields in fiscalcalendar :id, calendar_id,fiscalyearweek
fields in voucherdata :id, vhour, fiscalyear week
I want the sum of the vhour for each month
I can get it to group by fiscal week by doing
Voucherdata.sum(:vhour,:group=>:fiscalyearweek)
is there a simpler way to get it by month?

This should do what you're asking for, assuming I understand your database relationship.
Voucherdata.sum(:vhour, :joins => :calendars, :group=> 'calendars.monthname)
However this statement won't work without a little modification. You're not telling Rails how to link Voucherdata and Fiscalcalendar. With two :has_many relationships Rails doesn't know where to find the foreign key to link to the other one.
You need to make a join model and either make it a :has_many, :through relationship or use a :has_and_belongs_to_many relationship. Once you've set that up the above statement will work without modification.
Corrected model relationship and migration required. Using a :has_and_belongs_to_many relationship (cleaner syntax):
class Calendar < ActiveRecord::Base
has_many:fiscalcalendars
has_many:voucherdatas ,:through => :fiscalcalendars
end
class Fiscalcalendar < ActiveRecord::Base
belongs_to :calendar
has_and_belongs_to_many :voucherdatas
end
class Voucherdata < ActiveRecord::Base
has_many :calendars, :through => :fiscalcalendars
has_and_belongs_to_many :fiscalcalendars
end
class CreateFiscalcalendarsVoucherdatasJoinTable ActiveRecord::Migration
def self.up
create_table :fiscalcalendars_voucherdatas :id => false do |t|
t.integer :fiscalcalendar_id
t.integer :voucherdata_id
end
end
def self.down
drop_table :fiscalcalendars_voucherdatas
end
end

Related

how to create a record for a join table?

I have the following associations set up:
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :users_books
has_many :users, through: :user_books
end
and
class User < ActiveRecord::Base
has_many :users_books
has_many :books, through: :users_books
end
I created a join table migration as I ought to
class CreateUsersBooks < ActiveRecord::Migration[4.2]
def change
create_table :users_books do |t|
t.integer :user_id
t.integer :book_id
end
end
end
Now I need to create a method called check_out_book, that takes in a book and a due_date as arguments. When a user checks out a book, it should create a new UserBook record (or Checkout record or whatever you want to call you join table/model). That new UserBook record should have a attribute (and therefore table column) of returned? which should default to false. How would I go about creating this method and the migrations?
Your tablenames and your associations in Rails should always be singular_plural with the exception of the odd duckling "headless" join tables used by the (pretty useless) has_and_belongs_to_many association.
class CreateUserBooks < ActiveRecord::Migration[4.2]
def change
create_table :user_books do |t|
t.references :user
t.references :book
t.boolean :returned, default: false
end
end
end
class UserBook < ActiveRecord::Base
belongs_to :user
belongs_to :book
end
class Book < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :user_books
has_many :users, through: :user_books
end
class User < ActiveRecord::Base
has_many :user_books
has_many :books, through: :user_books
end
But you should really use a better more descriptive name that tells other programmers what this represents in the domain and not just a amalgamation of the two models it joins such as Loan or Checkout.
I would also use t.datetime :returned_at to create a datetime column that can record when the book is actually returned instead of just a boolean.
If you want to create a join record with any additional data except the foreign keys you need to create it explicitly instead of implicitly (such as by user.books.create()).
#book_user = Book.find(params[:id]).book_users.create(user: user, returned: true)
# or
#book_user = current_user.book_users.create(book: user, returned: true)
# or
#book_user = BookUser.new(user: current_user, book: book, returned: true)

Has many :through association not found

I have two models that can have tags added to them.
Player
Ticket
and I have a Tag model which belongs to both so I have two join models
tag_ticket
tag_player
I am getting a Could not find the association :tag_tickets in model Ticket error but my association is in there.
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
I'm just focusing on the Ticket model but the player model should look similar.
this is my migration for TagTicket
class CreateTagTickets < ActiveRecord::Migration
def change
create_table :tag_tickets do |t|
t.integer :ticket_id
t.integer :tag_id
t.timestamps
end
end
end
You need to specify the :tag_tickets join first like this:
class Ticket < ActiveRecord::Base
has_many :tag_tickets
has_many :tags, :through => :tag_tickets
end
You would also need to specify the joins in your TagTicket model:
class TagTicket < ActiveRecored::Base
belongs_to :ticket
belongs_to :tag
end
Alternatively, you can skip all this and use a habtm join (only recommended if the tag_tickets join is truly only used as a join and has no primary key for itself). In this case you would have no TagTicket model (just a tag_tickets table) and the Ticket model would look like this:
class Ticket < ActiveRecord::Base
has_and_belongs_to_many :tags
end

Help with ActiveRecord query

Models:
class User < ActiveRecord::Base
has_many :attendances
has_many :courses, :through => :attendances
end
class Course < ActiveRecord::Base
has_many :attendances
end
class Attendance < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
Migrations:
create_table(:users) do |t|
t.string :name
end
create_table(:courses) do |t|
t.string :name
end
create_table(:attendances) do |t|
t.references :user, :course
t.date :date
end
Question
I would like to query:
a list of all courses
the date of a given user's last attendance (if any) for each course
What is the best way to tie the following together into a single query?
#courses = Course.all
#user = User.first
#attendances = #user.attendances.group('course_id').order('date DESC')
Note that there is a requirement to include courses that a user has not yet attended.
Any advice much appreciated.
Update
The result I am looking for is as follows:
Course Last attended
===============================
Some course 2011-03-09
More training Not Attended
Another course 2010-12-25
In SQL, I would write this query as:
SELECT * FROM courses AS c
LEFT OUTER JOIN attendances AS a ON a.course_id=c.id
WHERE a.user_id=1
GROUP BY a.course_id
ORDER BY a.date DESC;
I could always execute this SQL from within Rails, but I would prefer to avoid this and do things "the Rails way" if possible.
The "Rails-Way" is to define small finders in the model and then to chain them in the controllers.
In class Attendance you could define a new method
def self.last_attendance
maximum("date")
end
In class Corse ...
def self.ordered
order("name DESC")
end
And so on. In the controller then, you use them in different combinations. The big advantage of this approach is
Reusability of finder methods
Decoupling of classes
Better readability of code
Your has_many through relation should be like this:
class User < ActiveRecord::Base
has_many :attendances
has_many :courses, :through => :attendances
end
class Course < ActiveRecord::Base
has_many :attendances
has_many :users, :through => :attendances
end
class Attendance < ActiveRecord::Base
belongs_to :user
belongs_to :course
end
And please explain your problem. Because
#user.courses will give you only related courses
Course.all will give you all courses
Edited:
Course.find(:all, :joins => 'LEFT OUTER JOIN attendances ON attendances.course_id =
courses.id', :conditions => ['attendances.user_id = ?', 1], :group =>
'attendances.course_id', :order => 'attendances.date DESC')
I hope it will work for you.

Identifying the relationship with multiple one-to-one relationships between two models in rails

I have the following two models:
class Project < ActiveRecord::Base
has_one :start_date, :class_name => 'KeyDate', :dependent => :destroy
has_one :end_date, :class_name => 'KeyDate', :dependent => :destroy
and
class KeyDate < ActiveRecord::Base
belongs_to :project
Given a certain key date from the database related to a project:
#key_date = KeyDate.find(:first)
is there a way to introspect the relationship to check if the #key_date is related to the project as start_date or as end_date?
A nice way would be to use single table inheritance for the KeyDate class
class KeyDate < ActiveRecord::Base
belongs_to :project
end
class StartDate < KeyDate
end
class EndDate < KeyDate
end
class Project < ActiveRecord::Base
has_one :start_date, :dependent => :destroy
has_one :end_date, :dependent => :destroy
end
class CreateKeyDatesMigration < ActiveRecord::Migration
def up
create_table :key_dates do |t|
t.date :date
t.string :type #this is the magic column that activates single table inheritance
t.references :project
end
end
…
end
this lets you do
#key_date = KeyDate.find(:first)
#key_date.type # => "StartDate"
One clean way to do what you want is to create STI:
http://api.rubyonrails.org/classes/ActiveRecord/Base.html
See one example I gave here:
Rails devise add fields to registration form when having STI
Just thinking aloud...
class KeyDate < ActiveRecord::Base
belongs_to :project
def start_date?
project.start_date == self
end
def end_date?
project.start_date == self
end
date_type
[:start_date, :end_date].find {|sym| send("#{sym}?") }
end
end
To be honest I can't see why you'd ever need this. Surely you're always going to have a handle on a project anyway?

In RoR, how do I create TWO one to one relationship between two tables?

In RoR3,
I have Users and Skills and each skill is created by a user. I wanted to record that, so I created a one to many relationship.
class User < ActiveRecord::Base
has_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :user
end
However, each user also has many skills in the sense that, user "Bob" created skill "Kung Fu", user "Charlie" created skill "Karate" and user "Bob" both created and is able to do both "Kung Fu" and "Karate"
How should I represent this with ActiveRecord? Should I just create a new table "user_skills" which has_many :skills? and belong_to :user?
There are two different associations here. The first is a one-to-many association. An user can be the creator of any number of skills. The second one is a many-to-many association, an user can have many skills and a skill can have many users.
The first one is a simple belongs_to <-> has_many declaration. For the second one, you either need a has_and_belongs_to_many declaration in both models, and a related join table, or a dedicated join model, and a has_many :through declaration. Let's try the first one:
Method 1: HABTM
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_and_belongs_to_many :users
end
This requires a join table called "skills_users" that has columns named user_id and skill_id
Method 2: Has many through (Join model)
The second one is similar, but adds a model that acts as the middleman. This has an added benefit that you can include additional columns in the join model, like for example a skill level.
class User < ActiveRecord::Base
has_many :created_skills, :class_name => 'Skill', :inverse_of => :creator
has_many :user_skills
has_many :skills, :through => :user_skills
end
class Skill < ActiveRecord::Base
belongs_to :creator, :class_name => 'User', :inverse_of => :created_skills
has_many :user_skills
has_many :users, :through => :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
Having those two models
class User < ActiveRecord::Base
has_and_belongs_to_many :skills
end
class Skill < ActiveRecord::Base
has_and_belongs_to_many :users
end
You would have to create an extra migration (without the model)
rails generate migration CreateSkillsUsersJoin
which will give you
class CreateSkillsUsersJoin < ActiveRecord::Migration
def self.up
create_table :skills_users, id => false do |t|
t.references "user"
t.references "skill"
end
add_index :skills_users,["user_id","skill_id"]
end
def self.down
drop_table :skills_users
end
end
The methods self.up and self.down you will have yo add them
You'd be well served using a gem like acts_as_taggable_on which you'd be able to simply setup and use in your User model, something like:
acts_as_taggable_on :skills
Honestly, they've figured all this stuff out, as it's not as simple as what you're trying to do, OR I should rephrase that and say, what you are trying to do is overtly 'complex' and this gem allows you to just keep on, keeping on after it's set up.
Read the Readme.

Resources