I have the following Scope on a resource page:
scope("Current Active Event Registrations") { |scope| Event.current_active_event_registrations }
The error I keep getting when viewing the page is:
undefined method `except' for nil:NilClass
c = c.except :select, :order
The code in Event looks like:
class Event < ActiveRecord::Base
has_many :registrations
scope :active_event, -> { where(active: true) }
scope :not_expired_active, -> { active_event.where('end_at > ?', DateTime.now) }
after_save :check_for_other_active_events
def random_winner
self.registrations.order("RANDOM()").first
end
def self.current_active_event_registrations
events = self.not_expired_active
events.first.registrations unless events.blank?
all if events.blank?
end
private
def check_for_other_active_events
Event.where('id != ?', self.id).update_all(active: false) if self.active
end
end
I am just wanting to add in a custom scope to my Registration Resource page in my ActiveAdmin backend.
I'm using Rails 4 and the latest ActiveAdmin
def self.current_active_event_registrations
events = self.not_expired_active
if events.blank?
all
else
events.first.registrations
end
end
Related
I am trying to unscope multiple model as below
User Model which has acts_as_paranoid
class User
acts_as_paranoid
has_one :category
has_one :brand
has_one :item
INDEXED_FIELDS = {
only: [:name],
include: {
category: { only: [:name] },
item: { only:[:name] },
brand: { only: [:name]},
}
}
def custom_json
Category.unscoped do
Item.unscoped do
Brand.unscoped do
self.as_json(INDEXED_FIELDS)
end
end
end
end
end
User model has following association which also has acts_as_paranoid
Sample Category model, Brand and Item model have same code
class Category
acts_as_paranoid
belongs_to :user
end
Can I do this dynamically with 'N' number of models, like iterating over array as below
def custom_json
[Category, Item, Brand].each do
# do unscoping
end
end
Association looks like
I think the approach you may have is to unscope the class manually, by setting default_scopes to [], and then putting it back.
classes_to_unscope = [Category, Item, Brand]
# remove default_scopes, saving them in previous_scopes
previous_scopes = classes_to_unscope.map do |klazz|
scopes = klazz.default_scopes
klazz.default_scopes = []
scopes
end
self.as_json(INDEXED_FIELDS)
# put default_scopes back
classes_to_unscope.each_with_index do |klazz, i|
klazz.default_scopes = previous_scopes[i]
end
As extra method:
def unscope_all(*models, &block)
# the order does not matter, but preserve it
blocks = [block] + models.reverse.map do |model|
proc do |inner_block|
model.unscoped { inner_block.call }
end
end
blocks.inject do |inner, outer|
proc { outer.call(inner) }
end.call
end
Then you would use it:
unscope_all(Category, Item, Brand) do
# do unscoping
end
unscoped pitfall: when leaving the block you loose the "unscopability", so make sure you don't return a relation (it won't be unscoped). Instead you have to resolve it in the block (e.g. by returning an array where(...).to_a.
Hello I am trying to convert the method self.liked_by(user) into a scope. I am not entirely sure what my instructor is asking for so any interpretations on the question are greatly appreciated.
this is the method in question that I am supposed to turn into a scope.
def self.liked_by(user)
joins(:likes).where(likes: { user_id: user.id })
end
this is where the method appears in the model
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :topic
has_many :likes, dependent: :destroy
before_validation :httpset
validates :url, format: { with: /\Ahttp:\/\/.*(com|org|net|gov)/i,
message: "only allows valid URLs." }
def self.liked_by(user)
joins(:likes).where(likes: { user_id: user.id })
end
def httpset
if self.url =~ /\Ahttp:\/\/|\Ahttps:\/\//i
else
if self.url.present?
self.url = "http://"+ self.url
else
self.url = nil
end
end
end
end
And this is where the method is called in the controller
class UsersController < ApplicationController
def show
user = User.find(params[:id])
#bookmarks = user.bookmarks
#liked_bookmarks = Bookmark.liked_by(user)
end
end
Thanks for looking at my problem and have a good day.
#liked_bookmarks = Bookmark.liked_by(user)
In this line, in the same way you send the user parameter to a method, the same way you can send it to a scope.
class Bookmark < ActiveRecord::Base
---------
---------
scope :liked_by, ->(user) { joins(:likes).where(likes: { user_id: user.id }) }
---------
---------
end
the parameter you sent from the scope call can be accessed using the (user{or any name) in the scope
reference of scopes
As Owen suggested, read the docs to understand what scopes are. It is just another syntax to define your model's class methods (just like the one you already have).
scope :liked_by, ->(user) { joins(:likes).where(likes: { user_id: user.id }) }
I have a index view in my rails application that allows filtering via search params. When a group op articles are returned its is wropped in an articles colllection like {"articles":[{"article":{"id":341,"updated":"2015-08-18T13:05:08.427Z","title":". But if only a single object is found the articles level is missing, {"article":{"id":398,"updated":"2015-08-07T11:37:26.200Z","title":. How can I fix it so that a single object behaves like multiple?
_articles.list.json.jbuilder
require 'uri'
require 'publish_on'
json.cache! ['v1', articles] do
json.articles articles do |article|
json.cache! ['v1', article] do
json.article do
json.id article.id
json.updated as_ns_date(article.updated_at)
json.title article.label
json.numberOfViews article.view_mappings.count
json.numberOfFavorites article.favorite_mappings.count
json.imageURLs article.images if article.images.any?
json.youtubeURL article.youtube unless article.youtube.blank?
json.tags article.categories.map(&:label)
json.isFeatured article.featured
json.isPublished article.is_published
json.published as_ns_date(article.publish_on)
end
end
end
end
index.json.jbuilder
json.partial! 'articles/articles_list', articles: #articles
articles_controller.rb
def index
#articles = SearchArticlesCommand.new(params).execute
render :index
end
search_articles_command.rb
class SearchArticlesCommand
def initialize(params = {})
#since = params[:since_date]
#keys = params[:search_query]
#category = params[:category]
end
def execute
Article.unscoped do
query = if #since.present?
Article.article.since_date(#since)
else
Article.published_article
end
query = query.search_by_keywords(#keys) if #keys.present?
query = query.search_by_category(#category) if #category.present?
query.select(:id, :updated_at, :label, :is_published, :featured, :slug, :created_at).order(created_at: :desc)
end
end
end
article.rb
class Article < Comfy::Cms::Page
include PgSearch
include ActionView::Helpers::SanitizeHelper
HOSTNAME = ENV['HOSTNAME'] || Socket.gethostname
has_many :view_mappings, dependent: :destroy
has_many :favorite_mappings, dependent: :destroy
pg_search_scope :search_by_keywords, against: [:content_cache, :label], using: { tsearch: { any_word: true, prefix: true } }
pg_search_scope :search_by_category, associated_against: {
categories: [:label]
}
scope :since_date, -> (date) { where('created_at > ? OR updated_at > ? ', date, date) if date.present? }
scope :folder, -> { where.not(layout_id: ENV['ARTICLE_LAYOUT_ID']) }
scope :published_article, -> { published.article }
scope :article, -> { where(layout_id: ENV['ARTICLE_LAYOUT_ID']) }
It is what i suspected. If you want the same behavior your query should return the same type of object when it finds one or many articles. The problem is that either you are returning an ActiveRecordRelation or a Article object depending on your params.
#articles = Article.all # => ActiveRecordRelation, an array per se
#articles = Article.find(1) # => Article object
When it comes to jbuilder to construct the JSON it checks if it is an array of objects and then wrap the json with a { keyword => array }. WHen it is a single object, it defaults to a single object {article: {}}.
The solution is simple, you can tweak your SearchArticlesCommand to always return an ActiveRecordRelation, even if it finds only one object.
In apps/models/concerns/deactivable.rb
module Deactivatable
extend ActiveSupport::Concern
included do
scope :alive, -> { where(:deactivated_at => nil) }
end
def deactivate(t = Time.current)
update_attribute(:deactivated_at,t)
end
def activate
update_attribute(:deactivated_at,nil)
end
def deactivated?
deactivated_at.present?
end
end
This is being included in 2 models, app/models/activity_rules/activity_detection_rule.rb and app/models/concerns/generic_campaign.rb.
There are 2 more models which contain the same methods with different attribute name.
In redeemable.rb,
class Redeemable < ActiveRecord::Base
scope :alive, -> { where("(deactivation_date is null) and (expiry_date is null or expiry_date >= ?)",Date.today) }
def deactivate(t = Time.current)
update_attribute(:deactivation_date,t)
end
def reactivate
update_attribute(:deactivation_date,nil)
end
def deactivated?
deactivation_date.present?
end
end
and in surprise_set.rb
scope :alive, -> { where("deactivation_date is null") }
with the same 3 methods as redeemable.rb.
How to use Deactivable concern to DRY up the other two models?
You could return the attribute that indicates the time of deactivation from a class method. You can provide a default implementation in your concern and override in the class that includes the concern if you need to:
module Deactivatable
extend ActiveSupport::Concern
included do
scope :alive, -> { where(deactive_attr => nil) }
def self.deactive_attr
:deactivated_at
end
end
def deactivate(t = Time.current)
update_attribute(self.class.deactive_attr, t)
end
def activate
update_attribute(self.class.deactive_attr, nil)
end
def deactivated?
self.send(self.class.deactive_attr).present?
end
end
Then, in classes where you want to provide a different attribute you can add a class method:
include Deactivatable
def self.deactive_attr
:deactivation_date
end
You could also DRY up your alive scope a bit by allowing the class that includes the concern to define the conditions for 'aliveness'. In the concern you can define the default
scope :alive, -> { where(self.active_conditions) }
def self.active_conditions
{ self.deactive_attr => nil }
end
You can then provide a different implementation of active_conditions in the class itself:
self self.active_conditions
["(deactivation_date is null) and
(expiry_date is null or expiry_date >= ?)", Date.today]
end
I have code like this:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return self
end
end
end
end
I need to call it like this:
Item.where("created_at > ?", Time.now - 1.week).for_category(#category)
#category may or may not be null. In the case where category is null, I want the method to simply pass through and return the relation unchanged. Of course, return self simply returns the Item class.
What would be the correct way to do this?
Are you trying to return the Scope (as opposed to the Class itself) for further scope action? If so, then something like the following should work:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return scoped
end
end
end
end
HTH
class Item < ActiveRecord::Base
def self.for_category(c)
conditions = {:category_id => c.id}
conditions.delete_if {|key,val| val.blank? }
self.where(conditions)
end
end
Your Item is associated with Category ? If yes then you can simply get all item categories by Item.where("created_at > ?", Time.now - 1.week).categroies not need for above code.
#scoped was deprecated in Rails 4. You can use #all to achieve the same effect:
class Item < ActiveRecord::Base
class << self
def for_category(c)
if c
return where(:category_id => c.id)
else
return all
end
end
end
end