Pundit Scoping for Model Ownership through a Join table - Rails 4 - mysql

I'm associating users with given firms through a join table because I need to be able to have a bunch of users with every firm and vice versa.
class User
has_many :firm_connections, dependent: :destroy
has_many :firms, through: :firm_connections
end
class FirmConnection
belongs_to :user
belongs_to :firm
end
class Firm
has_many :firm_connections
has_many :users, through: :firm_connections
end
My question is, when a user hits the index page for firms, how do I scope it to only show what those users are associated with?
class FirmPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where #only the firms associated with that user
end
end
end
Do I need to create a scope at the firm level that accepts a #user? Or can I do this all directly inline? I could hack something together, but haven't wrapped my head around pundit yet, so any direction would be greatly appreciated!
like this:
def self.associated_with(user)
all.select { |m| m.users.include?(user) }
end

This should work for you
class Firm
def index
#firms = policy_scope(Firm)
end
end
class FirmPolicy < ApplicationPolicy
class Scope < Scope
def resolve
if user.admin?
scope.all
else
user.firms #only the firms associated with that user
end
end
end
end
The policy doesn't always have to call it the way that you think, it just has to return something (for scopes, almost always an ActiveRecord::Relation, for regular, true or false). You could do
scope.includes(:firm_connections).where(firm_connections: { user_id: user.id })
but that's not as readable (IMO).

Related

Rails: active-records query for entry in range & included in

I am working on a shipping implementation for a checkout process.
My app has carts, cart_items, orders and order_items.
Weight and size of all items are in the database and I calculate total_weight in the order and cart models. I also have a shipping_service model with weightmin and weightmax for each shipping service + a postzone and land (country) model.
Now I would like to show on the shopping cart page only the shipping services which are conform to the weight of the cart or order.
I suppose my carts_controller should be something like:
class CartsController < ApplicationController
def show
#cart = Cart.find(params[:id])
#lands = Land.find(:all)
#shippingservices = Shippingservice.where('#cart.total_weight BETWEEN ? AND ?', :weightmin, :weightmax)
end
My cart model is:
class Cart < ActiveRecord::Base
attr_accessor :total_weight
has_many :cart_items
has_many :products, :through => :cart_items
has_many :lands
has_many :shipping_services, :through => :postzones
def total_weight
cart_items.inject(0) {|sum, n| n.weight * n.amount + sum}
end
end
My land model is
class Land < ActiveRecord::Base
has_many :shippingservices, :through => :postzones
has_many :postzones
has_many :carts
end
My shipping_service model is:
class Shippingservice < ActiveRecord::Base
has_many :lands, :through => :postzones
has_many :postzones
has_many :carts
has_many :orders
end
My postzone model is:
class Postzone < ActiveRecord::Base
belongs_to :shippingservice
belongs_to :land
end
The postzone table has foreign keys for lands and shipping_services.
Latter I would like to implement two selector fields: one for ship_to_countries and one for shipping_services, with the second selector being populate only with entries related to the entry selected in the first selector.
I had already this working inside the carts_controller:
#shippingservices = Shippingservice.includes(:lands, :postzones).where('postzones.land_id = ?', Land.first.id)
Which load only shipping services for a specific country into the second selector. But I do not know how to combine the two where clauses relative to weight and postzone into one query.
Any help is very much appreciated!
Thank you in advance.
The method total_weight is a ruby method which is defined in the model Cart
Then you cannot call this method within an SQL statement.
You need to calculate the total weight in the SQL statement.
You should try something like
#shippingservices = Shippingservice.joins(carts: :cart_items).where(
'(cart_items.weight * cart_items.amount) BETWEEN ? AND ?', :weightmin, :weightmax
)
I didn't try but I think it should work :)

pre-load nested resource with condition

In my simple attendance app, there are :students, :semesters, :attendances. Attendance has columns student:references semester:references date:date present:boolean.
semester.rb
class Semester < ApplicationRecord
has_and_belongs_to_many :students
accepts_nested_attributes_for :students
end
student.rb
class Student < ApplicationRecord
has_and_belongs_to_many :semesters
has_many :attendances, dependent: :destroy
accepts_nested_attributes_for :attendances
end
attendance.rb
class Attendance < ApplicationRecord
belongs_to :semester
belongs_to :student
validates_presence_of :date
end
In the semesters#show page, I want to display each student in that semester, and each student's attendance percentage like below.
It works, but I have to filter through some :attendances that aren't associated with the semester before I start count. So my goal is to eager-load the semester, its students, and their attendances that only belong_to that semester.
This way, when I use
#semester.students.each do |student|
student.attendances
end
The .attendances method should only return those associated with that semester. Is this possible?
Here's what I got
# semesters_controller.rb
def show
#semester = Semester.includes(students: [:attendances])
.order('students.first_name')
.find params[:id]
end
# students_helper.rb
def student_attendance(student)
total = student.attendances.select { |x| x.semester_id == #semester.id }
present = total.select &:present
percent = (present.size/total.size.to_f * 100).round rescue 0
link_to student, class: 'attendance', style: "width: #{percent}%" do
<<-HTML.html_safe
<span>#{student.first_name}</span>
<span>#{percent}%</span>
HTML
end
end
I've found that using select {|x| x.semester_id == #semester.id } instead of where semester_id: #semester.id and select &:present instead of where present: true reduces the number of queries.
Anyways, is there a way that I can load the :attendances so that I don't have to go through that first filter (select {|x| x.semester_id == #semester.id })? If I don't filter like I am doing, then it will show that student's attendance percentage for ALL semesters they've ever been in, instead of just this one semester we're trying to show on the #show page.
I just don't want to be loading all that unnecessary data, nah mean? Thanks.
Looks like you already have a way of connecting an attendance directly with a semester (as belongs_to :semester is stated in your Attendance class).
Have you tried:
class Semester < ApplicationRecord
has_and_belongs_to_many :students
has_many :attendences
end
attendences = #semester.attendences
OR just:
attendences = Attendence.where(semester: params[:id])
(you can use appropriate joins/includes to reduce sql-queries)

Call two models dependent on each other in one controller for a view

I am new to Rails so I am going to try and explain this the best I can.
I have three models: artist, fest, and festival_artist
artist contains only an ID and an artist_name
fest contains only an ID and a festival_name
festival_artist contains an ID, a artist_id, and a festival_id
I created Fest using a scaffold so that is where my controller and show.html.erb is.
Below are my models:
class Artist < ApplicationRecord
belongs_to :festival_artist
end
class Fest < ApplicationRecord
belongs_to :festival_artist
end
class FestivalArtist < ApplicationRecord
has_many :artists
has_many :fests
end
In my fests_controller.rb I have:
def show
#festival_artists = FestivalArtist.where(festival_id: #fest.id)
end
I tried to add:
def show
#festival_artists = FestivalArtist.where(festival_id: #fest.id)
#artists = Artist.where(id: #festival_artists.artist_id)
end
However, that throws an undefined method artist_id for # error.
The goal is to display the Artist's name in the Fest's show.html.erb page for the festival that that artist belongs to.
In SQL it would be:
SELECT A.artist_name
FROM festival_artists AS FA
INNER JOIN artists AS A
ON FA.artist_id = A.id
Any suggestions? Even telling me what to Google would help out because I'm not sure my terminology is correct.
Let me know if you need anymore information.
Guess your models structure is not 100% correct. Try to check out http://guides.rubyonrails.org/association_basics.html for details.
There are to ways to handle your associations in Rails:
HABTM (has and belongs to many), as noticed in #grizzthedj answer.
has_many :through association
In this case your code will look like
class Artist < ApplicationRecord
has_many :festival_artists
has_many :fests, through: :festival_artists
end
class Fest < ApplicationRecord
has_many :festival_artists
has_many :artists, through: :festival_artists
end
class FestivalArtist < ApplicationRecord
belongs_to :artists
belongs_to :fests
end
So you can access artists in the controller
def show
#festival_artists = #fest.artists
end
I'm not sure that you need the FestivalArtist model. If you use "has_and_belongs_to_many" in Artist and Fest models, this will implement the many-to-many relation that you are looking for.
# fest.rb
class Fest < ActiveRecord::Base
has_and_belongs_to_many :artists
end
# artist.rb
class Artist < ActiveRecord::Base
has_and_belongs_to_many :fests
end

Rails MySQL query with Single Table Inheritance N+1 issue

I am trying to find all users that signed up during a given period of time to the ActionMovie plan. I am running into an N+1 problem and it's taking me a very long time to get the number of new signups. I was wondering if there was any creative thing I could do with arel_tables or something like that that could help cut down on this process?
My current code looks similar to the below:
#find all UserMovies created during time frame
user_movies = UserMovie.where(:created_at => start_time..end_time)
#find users
users = user_movies.collect {|um| um.user}
#iterate through each users user_movies and see if the their first action movie was during the time frame I am looking for
users.each do |user|
user_movies_array = user.user_movies.map {|um| {um.movie.type => um.created_at}}
user_movies_array.each do |um|
if um["ActionMovie"] > start_time
puts "new user"
end
end
end
Class User
has_many :user_movies
has_many :movies, :through => :user_movies
end
Class Movie
has_many :user_movies, :foreign_key => :movie_id
has_many :users, :through => :user_movies
end
Class UserMovie
belongs_to :user
belongs_to :movie
end
Class ActionMovie < Movies
end
Class SuspenseMovie < Movies
end
Have you tried eager loading the Movie association using the :include option?
Take a look at the API docs for #has_many to see specific implementation and scroll to the top to the section called Eager loading of associations to see a general overview.

Activerecord - how to made this?

Everyday, I need to run the a script and send all of my users an 'exam' or set of questions. I have modelled as class 'Exam' which subclasses ActiveRecord::Base. Now, how do I send user's instances of Exam?
What I was thinking was create a new class called 'ExamInstance' which would have a reference to 'Exam' and the user.
I am new to SQL and ActiveRecord so if someone can help me better model this so I can avoid problems later on or just give me some insight, that would be great.
Thanks
I'll suggest just use has_many :through create a model UserExam for many to many relation between exam and user
class User < ActiveRecord::Base
  has_many :users_exams
  has_many :exams, :through => :users_exams
end
 
class UserExam < ActiveRecord::Base
  belongs_to :users
  belongs_to :exams
end
 
class Exam < ActiveRecord::Base
  has_many :users_exams
  has_many :users, :through => :users_exams
end
For more information on has_many :through
Add 'ExamUser' model to keep track of exam and the corresponding users references. The model skeleton will look something like:
class ExamUser < ActiveRecord::Base
belongs_to :exam
belongs_to :user
end
You could then loop through the records of this table to send your questions.
You can design your new model like this..
class ExamUser < ActiveRecord::Base
belongs_to :user # user has many exams
belongs_to :exam # exam has many users
end