How to unscope joins in RoR 4 - ruby-on-rails

Trying to unscope joins.
Example:
class MyModel < ActiveRecord::Base
default_scope -> { joins("JOIN other_table ON other_table.this_table_id = this_table.id") }
scope :without_relation -> { unscope(:joins) }
end
The problem is that it unscope ALL joins, even those that are automatically constructed by AR relations.

Here's what I did:
module UnscopeJoins
extend ActiveSupport::Concern
def unscope_joins(joins_string)
joins_values.reject! {|val| val == joins_string}
self
end
end
ActiveRecord::Relation.send :include, UnscopeJoins
Usage:
scope :without_joins, -> { unscope_joins("JOIN table on table2.id = table1.table2_id") }

Related

Create dynamic scopes based on other model

In a Rails (5.0) app, I have the following
class Batch < ApplicationRecord
belongs_to :zone, optional: false
end
class Zone < ApplicationRecord
scope :lines, -> { where(kind: 'line') }
end
Now I need to define in Batch a scope for each Zone which is a line. Something like the code below works.
Zone.lines.map(&:name).each do |name|
scope "manufactured_on_#{name}".to_sym, -> { joins(:zone).where("zones.name = '#{name}'") }
end
The issue is that the code above is evaluated when the app boots and at that time the scopes are created. If I add a newZone of kind line, the scope is not created.
Is there a way to solve this issue?
You could pass the zone's name as a scope param
scope :manufactured_on, -> (name) { joins(:zone).where(zones: { name: name } ) }
You can look into documentation and search for method_missing.
But this does not seem a good approach to me.
Instead, define scope in following way:
class Batch < ApplicationRecord
scope :manufactured_on, ->(line) { joins(:zone).where("zones.name = '#{name}'") }
end
Then simply use
Batch.manufactured_on(zone.name)
If you really need the scope name to be dynamic you can use method_missing as below:
class Batch < ApplicationRecord
belongs_to :zone, optional: false
def self.method_missing(name, *args)
method_name = name.to_s
if method_name.start_with?('manufactured_on_')
zone_name = method_name.split('manufactured_on_')[1]
joins(:zone).where(zones: { name: zone_name } )
else
super
end
end
def self.respond_to_missing?(name, include_private = false)
name.to_s.start_with?('manufactured_on_') || super
end
end
Now, you can call this scope as normal:
Batch.manufactured_on_zone_a

Rails association scope by method with aggregate

I'm trying to retrieve association records that are dependent on their association records' attributes. Below are the (abridged) models.
class Holding
belongs_to :user
has_many :transactions
def amount
transactions.reduce(0) { |m, t| t.buy? ? m + t.amount : m - t.amount }
end
class << self
def without_empty
includes(:transactions).select { |h| h.amount.positive? }
end
end
class Transaction
belongs_to :holding
attributes :action, :amount
def buy?
action == ACTION_BUY
end
end
The problem is my without_empty method returns an array, which prevents me from using my pagination.
Is there a way to rewrite Holding#amount and Holding#without_empty to function more efficiently with ActiveRecord/SQL?
Here's what I ended up using:
def amount
transactions.sum("CASE WHEN action = '#{Transaction::ACTION_BUY}' THEN amount ELSE (amount * -1) END")END")
end
def without_empty
joins(:transactions).group(:id).having("SUM(CASE WHEN transactions.action = '#{Transaction::ACTION_BUY}' THEN transactions.amount ELSE (transactions.amount * -1) END) > 0")
end

How to get children records of a scoped parent in Rails 5

I need a controller to pass along children records of parents that match a certain scope. I'd like that scope to be on the parent record.
class Parent < ApplicationRecord
has_many :children
scope :not_blue, -> { where(blue:false) }
scope :blue, -> { where(blue:true) }
# Subjective, may change in the future
scope :funny, -> { where('funny_scale>=?',5) }
scope :genies, -> { blue.funny }
end
class Child < ApplicationRecord
belongs_to :parent, required: true
end
class ChildrenController < ApplicationController
def index
# Yeah, this breaks horribly (and should), but you get my gist
#children_of_genies = Parent.genies.children
end
end
I know the answer is probably staring me in the face, but the right combination of google searches is eluding me.
If you'd like your solution to still be an ActiveRecord::Associations::CollectionProxy try Children.where(parent_id: Parent.genies.ids) which you then could turn into a scope.
scope: children_of_genies, -> { where(parent_id: Parent.genies.ids)
Scopes return an ActiveRecord_Relation, to get children for each member of it you can use collect:
#children_of_genies = Parent.genies.collect { |p| p.children }

Using another class scope in existing scope Activerecord

I want to use the scope of another class in the scope of the first class
so instead of
scope :active, -> {includes(:b).where(b: {column: 'ACTIVE'}).where(a: {column2: 'ACTIVE'})}
I want to be able to use a scope of b
scope :active, -> {includes(b.active).where(a: {column2: 'Active'})}
You can do this using merge:
scope :active, -> { includes(:b).merge(B.active)
.where(a: {column2: 'Active'}) }
Note: I used B to represent the model class for the b column or object.
Or, assuming you're in a's model already:
scope :active, -> { includes(:b).merge(B.active)
.where(column2: 'Active') }
Also, if you WANT eager loading then using includes is great. Otherwise, it's faster and less overhead to use joins, like this:
scope :active, -> { joins(:b).merge(B.active)
.where(column2: 'Active') }
I recommend to use scope on model, if it's admin specific, then can separate it to concern
http://api.rubyonrails.org/classes/ActiveSupport/Concern.html
module AdminUserScopes
extend ActiveSupport::Concern
included do
scope :admin_scope1, -> { includes(:b).where(b: {column: 'ACTIVE'}).where(a: {column2: 'ACTIVE'}) }
scope :admin_scope2, -> { admin_scope1.where(a: {column2: 'Active'}) }
end
end
# in your model
include AdminUserScopes
# in active_admin
scope :active, -> { admin_scope1 }
scope :active2, -> { admin_scope2 }
Upd:
If you want to use one condition to other model then can use merge
Dog.all.merge(User.males) # => select * from dogs where sex = 1;
If you want to use in association filtering, then:
Post.where(user: User.males) # => select * from posts where user_id in (select users.id from users where sex = 1)
In your case I guess you have A and B, and you want to get active A-records what connected to active B-records
# in A
scope :active, -> { where(column: 'ACTIVE') }
# in B
scope :active, -> { where(column2: 'ACTIVE', a: A.active) }
# in somewhere else
scope :active, -> { where(a: A.active) } # => have active A which have active B
p.s. it's much easier with more informative names, "A's" and "B's" are hard :)

rails 4 - sort by number of votes

So... I have images. and those images have votes.
I currently have image.rb
class Image < ActiveRecord::Base
belongs_to :user
belongs_to :event
has_many :image_votes, dependent: :destroy
default_scope { order(ci_lower_bound) }
def taken_by? (photographer)
self.user == photographer
end
def self.ci_lower_bound
pos = image_votes.where(value: 1).size
n = image_votes.size
if n == 0
return 0
end
z = 1.96
phat = 1.0*pos/n
(phat + z*z/(2*n) - z * Math.sqrt((phat*(1-phat)+z*z/(4*n))/n))/(1+z*z/n)
end
end
I've been playing around with this... the only way to get default scope to work is to use a method with self. I found that formula at http://www.evanmiller.org/how-not-to-sort-by-average-rating.html - how would I call this to make it work??
I'd create a new scope called by_votes, include sum() and order by this new column:
scope :by_votes, -> { select("#{Image.table_name}.*, sum(#{ImageVote.table_name}.votes) AS votes").order("votes DESC") }

Resources