Render an ActiveReccord:relationship with specific cat value - ruby-on-rails

My title is not really clear, I don't know how to formulate it, if you have suggestions, I can edit it.
I would like to render all the missions form my user_facturation. But I don't know how to do it with current db structure.
My relations :
mission has_many :mission_facturations
mission_facturation belongs_to :mission
Mission_facturation can be linked with many element facturation (user, partner, etc ...). So to save this relation I'm saving a fact_cat and a fact_id column. (A little bit has polymorphic relations)
Example, if I want to link my mission_facturation with the first user_facturation, I reccord :
mission_facturation.update(fact_cat: 8, fact_id: UserFacturation.first.id)
Now, I would like to access to all the missions directly from my user_facturation model. Initially, I was using that method into the model user_facturation.rb
def missions
MissionFacturation.where(fact_cat: 8, fact_id: self.id).where.not(mission_id: nil).map(&:mission)
end
But this is not rendering an ActiveReccord::Relation, so when I use it for a loop, it's not working well
Do you have any solutions to render a collection of missions into my user_facturation model ?

Start the query with the Mission model, something like this:
Mission.joins(:mission_facturations).where(mission_facturations: {fact_cat: 8, fact_id: self.id)
Or you can even set a relationship on the User class so AR handles the joins for you:
has_many :mission_facturations, -> { where(fact_cat: 8) }, :foreign_key => "fact_id"
has_many :missions, through: :mission_facturations

Related

optimizing page load with has_many and scope

I'm an experienced programmer who is relatively new to ruby/rails and databases. I have created a large website for signing up for courses. I thought I was being clever creating categories of course signups using scope and has_many. Logically, it encapsulates the information well, but my pages are now loading super-slow, and trying to eager load is confusing me.
I have models for Course, Person, CourseRole (student, teacher, etc.), and CourseSignup which includes one of each (Course, Person, CourseRole). It all works smoothly. Recently, I set up scopes in CourseRole to define the categories of signups (I had been hardcoding the role name, and wanted to get away from that). I then set up has_many relationships in Course for each of the categories. So, Course inherently has_many course_signups, and has my categories
has_many :student_signups, -> { CourseSignup.student }, class_name: 'CourseSignup', foreign_key: :course_id
has_many :teacher_signups, -> { CourseSignup.teacher }, class_name: 'CourseSignup', foreign_key: :course_id
etc. (I have 6 categories). I have a page that lists all courses and all of the signups for each course. Like:
Dodge Ball:
info about course
Students:
names of students
Teachers:
names of teachers
etc.
This page loads incredibly slowly. I was trying to add includes statements to the query (based on recommendations from the Bullet gem), but it actually makes it slower. This leads me to think I'm making this more complicated than I should, but I don't know enough to have a clue how to fix it. I imagine I should restructure my models. But I like the abstraction of the scopes/has_many
The page is generated by looping through rendering a partial which shows one course.
#courses = #cuco_session.assigned_courses.includes(:period).order('periods.start_time')
works but is very slow.
#courses = #cuco_session.assigned_courses
.includes(course_signups: [:person, :course_role])
.includes([:courses_rooms, :rooms])
.includes([:helper_signups, :student_signups, :volunteer_signups, :waiting_list_signups, :person_in_room_signups])
.includes(:period).order('periods.start_time')
Also works but is even slower.
We need more information about what is actually happening in the view to really answer why your page is slow.
Also a snippet of your log showing the actual SQL would be necessary.
You mention being new to using databases online with Rails. It's hard to give advice without more of a look into the structure of some of these models, but have you considered replacing CourseSignup with just a join table between Course and Person? It seems like that's what you're getting at here but it's hard to tell.
*has_many :student_signups, -> { CourseSignup.student }, class_name: 'CourseSignup', foreign_key: :course_id
has_many :teacher_signups, -> { CourseSignup.teacher }, class_name: 'CourseSignup', foreign_key: :course_id*
above code can be refactored using Single table inheritance
# app/models/person.rb
class Person < ApplicationRecord
#your code
end
# app/models/teacher.rb
class Teacher < Person
#your code
end
# app/models/student.rb
class Student < Person
#your code
end
For Course Role you can have a separate master table of Role and put references of Person table and Role table in Course Role table
I hope above solution put some light on your question.

Rails - Manipulating the returned objects in an ActiveRecord query

So I have the following simplified models and associations:
class Barber < User
has_many :barber_styles, inverse_of: :barber
end
class Style < ActiveRecord::Base
end
class BarberStyle < ActiveRecord::Base
belongs_to :barber, inverse_of: :barber_styles
belongs_to :style
delegate :name, to: :style
end
If I wanted to make a query based on all BarberStyle's that belong to a specific Barber, is there a way I can include the name of the associated Style?
I want to use a line something like:
BarberStyle.joins(:barber).where(barber: 109).include(:style)
So I'd be able to access an associated style.name. But I don't think a line like this exists.
I know I could just use map to build an array of my own objects, but I have to use a similar query with many different models, and don't want to do a bunch of extra work if it's not necessary.
In one of my previous projects, I was able to render json with the following lines:
#colleges = College.includes(:sports).where(sports: { gender: "male" })
render json: #colleges, include: :sports
But I don't want to render json in this case.
Edit:
I don't really have any unincluded associations to show, but I'll try to elaborate further.
All I'm trying to do is have extra associated models/fields aggregated to each BarberStyle result within my ActiveRecord Relation query.
In an attempt to make this less confusing, here is an example of the type of data I want to pass into my JS view:
[
#<BarberStyle
id: 1,
barber_id: 116,
style_id: 91,
style: { name: "Cool Hairstyle" }
>,
#<BarberStyle
id: 2,
barber_id: 97,
style_id: 92,
style: { name: "Cooler Hairstyle" }
>,
etc...
]
The reason I want the data formatted this way is because I can't make any queries on associated models in my JS view (without an AJAX call to the server).
I had very similarly formatted data when I did render json: #colleges, include: :sports in the past. This gave me a collection of Colleges with associated Sport models aggregated to each College result. I don't want to build json in this fashion for my current situation, as it will complicate a few things. But I suppose that's a last resort.
I'm not sure if there's a way to structure an ActiveRecord query where it adds additional fields to the results, but I feel like it should, so here I am looking for it. Haven't found anything in the docs, but then again there is sooooo much left out of those.
Doug as far as I understand your code, BarberStyle must be a joining model between Barber and Style. You mentioned in your comment that you removed the has_many :styles, through: :barber_styles from your Barber model because you thought that it wasn't relevant. That's not true, it's exactly the point that would help you to achieve your goal. Add this relation again then you can do something like this:
barber = Barber.includes(:styles).where(barber: {id: 109})
barber.styles.each { |style| puts style.name }
Since barber.styles is a collection, I added a loop between all the possible styles you can have. But, from that, you can use your data as you feel like, looping through it or any other way you want.
Hope to have helped!
First off, in your case inverse_of does not accomplish anything, since you are setting the default values. Remove that.
Secondly, it seems you need to better understand the concept of HABTM relationships.
Using has many through is generally a good idea since you can add data and logic to the model in the middle.
It ideally suits your case, so you should set it up like this:
class Barber < User
has_many :barber_styles
has_many :styles, through: :barber_styles
end
class Style < ActiveRecord::Base
has_many :barber_styles
has_many :barbers, through: :barber_styles
end
class BarberStyle < ActiveRecord::Base
belongs_to :barber
belongs_to :style
end
Now it is an easy task to get the styles of a given barber. Just do this:
barber = Barber.find(1)
barber.styles
# => AR::Collection[<#Style name:'Cool Hairstyle'>,<#Style name:'Cooler Hairstyle'>]
Rails automatically uses the BarberStyle model in between to find the styles of a certain barber.
I assume this covers your need, if you have extra information stored only in BarberStyle, let me know.

new to rails : relationships not setup right?

Right now I have some functionality working but its really ugly and I know there's a better way to do it.
Here's what I have right now in my User model:
def attending
dayseventsusers=self.days_events_users
daysevents=Array.new
events=Array.new
dayseventsusers.each do |deu|
daysevents<<DaysEvent.find(deu['days_events_id'])
end
daysevents.each do |de|
events<<Event.find(de['event_id'])
end
return events
end
I'm trying to get a list of events for a certain group of dayseventsusers records but I have to move through several relationships to accomplish this. What I'd really like to do is something like self.days_events_users.days_event.event, but I don't know if it can be this simple assuming my relationships are setup correctly.
User model
has_many :days_events_users
DaysEventsUsers model
belongs_to :user
belongs_to :days_event
DaysEvent
has_many :days_events_users
belongs_to :event
The days_events_users object belong to a single days_event, which belongs to a single event. Any suggestions are welcome!
Without having tested this, does the following work:
dayseventusers.collect(&:daysevent).collect(&:event)

Ruby on Rails Associations

I am starting to create my sites in Ruby on Rails these days instead of PHP.
I have picked up the language easily but still not 100% confident with associations :)
I have this situation:
User Model
has_and_belongs_to_many :roles
Roles Model
has_and_belongs_to_many :users
Journal Model
has_and_belongs_to_many :roles
So I have a roles_users table and a journals_roles table
I can access the user roles like so:
user = User.find(1)
User.roles
This gives me the roles assigned to the user, I can then access the journal model like so:
journals = user.roles.first.journals
This gets me the journals associated with the user based on the roles. I want to be able to access the journals like so user.journals
In my user model I have tried this:
def journals
self.roles.collect { |role| role.journals }.flatten
end
This gets me the journals in a flatten array but unfortunately I am unable to access anything associated with journals in this case, e.g in the journals model it has:
has_many :items
When I try to access user.journals.items it does not work as it is a flatten array which I am trying to access the has_many association.
Is it possible to get the user.journals another way other than the way I have shown above with the collect method?
Hope you guys understand what I mean, if not let me know and ill try to explain it better.
Cheers
Eef
If you want to have user.journals you should write query by hand. As far as I know Rails does has_many :through associations (habtm is a kind of has_many :through) one level deep. You can use has_many with finder_sql.
user.journals.items in your example doesn't work, becouse journals is an array and it doesn't have items method associated. So, you need to select one journal and then call items:
user.journals.first.items
I would also modify your journals method:
def journals
self.roles(:include => :journals).collect { |role| role.journals }.flatten.uniq
end
uniq removes duplicates and :inlcude => :journals should improve sql queries.
Similar question https://stackoverflow.com/questions/2802539/ruby-on-rails-join-table-associations
You can use Journal.scoped to create scope with conditions you need. As you have many-to-many association for journals-roles, you need to access joining table either with separate query or with inner select:
def journals
Journal.scoped(:conditions => ["journals.id in (Select journal_id from journals_roles where role_id in (?))", role_ids])
end
Then you can use user.journals.all(:include => :items) etc

Overwriting/Adding an ActiveRecord association dynamically using a singleton class

The business logic is this: Users are in a Boat through a join table, I guess let's call that model a Ticket. But when a User instance wants to check who else is on the boat, there's a condition that asks if that user has permission see everyone on the Boat, or just certain people on the Boat. If a User can see everyone, the normal deal is fine: some_user.boats.first.users returns all users with a ticket for that boat. But for some users, the only people that are on the boat (as far as they're concerned) are people in, let's say the dining room. So if User's ticket is "tagged" (using an acts_as_taggable style system) with "Dining Room", the only Users returned from some_user.boats.first.users should be Users with tickets tagged "Dining Room".
Just for the record, I'm not trying to design something to be insane from the getgo - I'm trying to wedge this arbitrary grouping into a (mostly) existent system.
So we've got:
class User
has_many :tickets
has_many :boats, :through => :tickets
end
class Ticket
belongs_to :user
belongs_to :boat
end
class Boat
has_many :tickets
has_many :users, :through => :tickets
end
Initially, I thought that I could conditionally modify the virtual class like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
has_many :tickets, :include => :tags, :conditions => ['tags.id in (?)', [#{tag_ids.to_s(:db)}]]
code
)
That gets all the way down to generating the SQL, but when generated, it generates SQL ending in:
LEFT OUTER JOIN "tags" ON ("tags"."id" = "taggings"."tag_id") WHERE ("tickets"._id = 1069416589 AND (tags.id in (5001,4502)))
I've tried digging around the ActiveRecord code, but I can't find anywhere that would prefix that 'id' in the SQL above with an underscore. I know that associations are loaded when an ActiveRecord class is loaded, and I'd assume the same with a singleton class. shrug.
I also used an alias_method_chain like:
singleton = class << a_user_instance ; self ; end
singleton.class_eval(<<-code
def tickets_with_tag_filtering
tags = Tag.find(etc, etc)
tickets_without_tag_filtering.scoped(:include => :tags, :conditions => {:'tags.id' => tags})
end
alias_method_chain :tickets, :tag_filtering
code
)
But while that approach produces the desired Tickets, any joins on those tickets use the conditions in the class, not the virtual class. some_user.boats.first.users returns all users.
Any type of comment will be appreciated, especially if I'm barking up the wrong tree with this approach. Thanks!
So a wild guess about your underscore issue is that Rails is generating the assocation code based on the context at the time of evaluation. Being in a singleton class could mess this up, like so:
"#{owner.table_name}.#{association.class.name}_id = #{association.id}"
You could get in there and define a class name property on your singleton class and see if that fixes the issue.
On the whole I don't recommend this. It creates behavior that is agonizing to track down and impossible to extend effectively. It creates a landmine in the codebase that will wound you or someone you love at a later time.
Instead, consider using a named_scope declaration:
class User
has_many :taggings, :through => :tickets
named_scope :visible_to, lambda { |looking_user|
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
}
end
While you may have to go back and edit some code, this is much more flexible in the ways it can be used:
Boat.last.users.visible_to( current_user )
It's clear that a restriction is being placed on the find, and what the purpose of that restriction is. Because the conditions are dynamically calculated at runtime, you can deal with the next weird modification your client hits you with. Say some of their users have xray vision and clairvoyance:
class User
named_scope :visible_to, lambda { |looking_user|
if looking_user.superhuman?
{}
else
{ :include => [ :tickets, :taggings ],
:conditions => [ "tickets.boat_id in (?) and taggings.ticket_id = tickets.id and taggings.tag_id in (?)", looking_user.boat_ids, looking_user.tag_ids ]
}
end
}
end
By returning an empty hash, you can effectively nullify the effect of the scope.
Why not just grab all users on the boat and include their tags.
Then run a quick filter to include & return only the users with the same tag as the inquiring user.
What version of Rails are you using? Have you tried upgrading to see if the underscore issue is fixed? It's like it can't find the foreign key to put in as "tag_id" or somethin'.
My ruby-fu is limited, so I'm not sure how to dynamically include the correct method options at run-time.
Just to help you clarify, you have to worry about this two places. You want to filter a user's viewable users so they only see users with the same tags. Your structure is:
user <--> tickets <--> boats <--> tickets <--> users
... right?
So, you need to filter both sets of tickets down to the ones with the current_user's tags.
Maybe you just need a current_user.viewable_users() method and then filter everything through that? I'm not sure what existing functionality you've got to preserve.
Blech, I don't feel like I'm helping you at all. Sorry.
Your approach is the problem. I know it seems expedient at the moment to hack something in where you don't have to refactor the existing call sites, but I believe given time this will come back to haunt you as the source of bugs and complexity.
Sleeping dogs that lie come back to bite you hard, in my experience. Typically in the form of a future developer who doesn't know your association is "magic" and uses it assuming it's just pail ole rails. He/she likely won't even have a reason to write a test case that would expose the behavior either, which raises the odds you'll only find out about the bug when it's live in production and the client is unhappy. Is it really worth the time you're saving now?
Austinfrombostin is pointing the way. Different semantics? Different names. Rule number one is always to write code that says what it does as clearly as possible. Anything else is the path of madness.

Resources