I'm trying to create a set of results to display on a 'Summary' page, but am having trouble filtering for the DateTime in the scope called :can_be_shown_now
When I change the date in the database, my Poster still appears when it should not.
Model
class Poster < ActiveRecord::Base
scope :poster_for_summary, -> { self.where(show_on_summary_page: true) }
scope :can_be_shown_now, -> { poster_for_summary.where((:start_time..:end_time).cover?(DateTime.now)) }
scope :choose_for_summary, -> { can_be_shown_now.order("RANDOM()").first }
end
Application Controller
def fallback_poster
#fallback_poster = Poster.choose_for_summary if controller_name == 'summary'
end
Any help is greatly appreciated. Thanks!
In your code cover? method refers to given range, not a query. You should try something like this:
scope :can_be_shown_now, -> { poster_for_summary.where(["`posters`.start_time <= ? AND `posters`.end_time >= ?", t = DateTime.now, t]) }
Related
I would like to access the lamda defined in a rails scope as the lambda itself and assign it to a variable. Is this possible?
So if I have the following scope
scope :positive_amount, -> { where("amount > 0") }
I would like to be able to put this lambda into a variable, like "normal" lambda assignment:
positive_amount = -> { where("amount > 0") }
So something like this:
positive_amount = MyClass.get_scope_lambda(:positive_amount)
For clarification, I'm wanting the body of the method that I generally access with method_source gem via MyClass.instance_method(method).source.display. I'm wanting this for on-the-fly documentation of calculations that are taking place in our system.
Our invoicing calculations are combinations of smaller method and scopes. I'm trying to make a report that says how the calculations were reached, that uses the actual code. I've had luck with instance methods, but I'd like to show the scopes too:
Edit 1:
Following #mu's suggestion below, I tried:
Transaction.method(:positive_amount).source.display
But this returns:
singleton_class.send(:define_method, name) do |*args|
scope = all
scope = scope._exec_scope(*args, &body)
scope = scope.extending(extension) if extension
scope
end
And not the body of the method as I'd expect.
If you say:
class MyClass < ApplicationRecord
scope :positive_amount, -> { where("amount > 0") }
end
then you're really adding a class method called positive_amount to MyClass. So if you want to access the scope, you can use the method method:
positive_amount = MyClass.method(:positive_amount)
#<Method: MyClass(...)
That will give you a Method instance but you can get a proc if you really need one:
positive_amount = MyClass.method(:positive_amount).to_proc
#<Proc:0x... (lambda)>
If I get your idea right. Here is one approach to do this
class SampleModel < ApplicationRecord
class << self
##active = ->(klass) { klass.where(active: true) }
##by_names = ->(klass, name) { klass.where("name LIKE ?", "%#{name}%") }
def get_scope_lambda(method_name, *args)
method = class_variable_get("###{method_name}")
return method.call(self, *args) if args
method.call(self)
end
end
end
So after that you can access the scopes like this:
SampleModel.get_scope_lambda(:by_names, "harefx")
SampleModel.get_scope_lambda(:active)
Or you can define some more class methods above, the one extra klass argument might be not ideal. But I don't find a way to access the self from inside the lambda block yet, so this is my best shot now.
By the way, I don't think this is a good way to use scope. But I just express your idea and to point it out that it's possible :D
UPDATED:
Here I come with another approach, I think it could solve your problem :D
class SampleModel < ApplicationRecord
scope :active, -> { where(active: true) }
scope :more_complex, -> {
where(active: true)
.where("name LIKE ?", "%#{name}%")
}
class << self
def get_scope_lambda(method_name)
location, _ = self.method(:get_scope_lambda).source_location
content = File.read(location)
regex = /scope\s:#{method_name}, -> {[\\n\s\w\(\):\.\\",?%\#{}]+}/
content.match(regex).to_s.display
end
end
end
So now you can try this to get the source
SampleModel.get_scope_lambda(:active)
SampleModel.get_scope_lambda(:more_complex)
Consider this code:
class Car
scope :blue, -> { where(color: "blue") }
scope :manual, -> { where(transmission: "manual") }
scope :luxury, -> { where("price > ?", 80000) }
end
def get_cars(blue: false, manual: false, luxury: false)
cars = Car.all
cars = cars.blue if blue
cars = cars.manual if manual
cars = cars.luxury if luxury
end
Is there a way to chain these scopes like Car.blue.manual.luxury conditionally? I.e. only scope if the arg is true?
You can use yield_self(read more here), new functionality added in ruby 2.5 for it.
In your example:
class Car
scope :blue, -> { where(color: "blue") }
scope :manual, -> { where(transmission: "manual") }
scope :luxury, -> { where("price > ?", 80000) }
end
def get_cars(blue: false, manual: false, luxury: false)
cars = Car.all
.yield_self { |cars| blue ? cars.blue : cars }
.yield_self { |cars| manual ? cars.manual : cars }
.yield_self { |cars| luxury ? cars.luxury : cars }
end
ActiveRecord scopes can be applied conditionally, like this:
scope :blue, -> { where(color: 'blue') if condition }
Where condition is something you define that returns true or false. If the condition returns true, the scope is applied. If the condition is false, the scope is ignored.
You can also pass values into a scope:
scope :blue, ->(condition) { where(color: 'blue') if condition }
So, you could do something like this:
Task.blue(color == 'blue')
Which is similar to what the OP requested. But, why would you?
A better approach is something like this:
scope :color, ->(color) { where(color: color) if color.present? }
Which would be called like this:
Car.color('blue') # returns blue cars
Car.color(nil) # returns all cars
Car.color(params[:color]) # returns either all cars or only cars of a specific color, depending on value of param[:color]
Car.color(params[:color]).transmission(params[:transmission]).price(params[:price])
Your mileage may vary.
Let's say I have some model
class MyModel < ApplicationRecord
scope :opened, -> { where(status: 'open') }
scope :closed, -> { where(status: 'closed') }
scope :colored, -> { where.not(color: nil) }
# etc
end
I can call scope chains like
MyModel.opened.colored
MyModel.send('opened').send('colored')
But how can I make scope chaining based on dynamic scope token list? I mean
scopes = ['opened', 'colored', ...]
The list may be very long and I need some general solution to do it as simple as possible, like MyModel.send_list(scopes).
More as result of scope, you can add like,
scope :send_list, -> (*scopes) { scopes.inject(self) { |out, scope| out.send(scope) } }
send this like YourModel.send_list(*scopes)
So let's say I want to check for nils in an ActiveRecord scope:
class Person < ActiveRecord::Base
scope :closest, ->(point) {
return nil unless point # How can I return the ActiveSupport::Relation here?
# other code goes below
}
end
You can just return self for it to return the default scope:
class Person < ActiveRecord::Base
def self.closest(point = nil)
point.nil? ? self : where(point: point)
end
end
Edit/Solution
As mentioned in the comments above, and as #ahmacleod pointed out, all is what we're looking for
scope :closest, ->(point) {
point.nil? ? all : where(point: point)
}
End edit
I think I have found what I am looking for and it's unscoped
scope :closest, ->(point) {
point.nil? ? unscoped : where(point: point)
}
The problem is that if I chain this, I would lose prior scopes if I use this after them.
You can set point parameter as optional. Something like this:
scope :closest, -> (point = '') {
where(point: point)
}
This way, the scope will return a ActiveRecord::Relation every time.
Hope this help :)
I have a Properties model which is using a field to sort by property type(properties.kind).
class Property < ActiveRecord::Base
scope :house, -> { where(kind: "House") }
scope :apartment, -> { where(kind: "Apartment") }
scope :commercial, -> { where(kind: "Commercial") }
end
Then in my Properties controller I'm combining them into an array so that I can group them by type in the view.
class PropertiesController < ApplicationController
def index
#house = Property.house.all
#apartment = Property.apartment.all
#commercial = Property.commercial.all
#listing = [#house, #apartment, #commercial]
end
The problem that stands out to me is that the model must perform at least 3 SQL queries on the same table for every index action. Obviously this is super inefficient, and ill be lucky if I can serve more than one user at a time.
My question is this: is there a better way to group an array's contents by a string value?
You can combine these into a single query and keep them scoped as follows:
scope :listing, -> { where(kind: ["House","Apartment","Commercial"]) }
Passing an array to Where is an alias in SQL for IN, which will cover all 3 options in one query. You could then split them out with a group_by for assigning them to instance variables.