How to check class in a policy's scope - ruby-on-rails

Project is with Rails + Pundit. Fruit class has two subclasses:
Tropical < Fruit
Temperate < Fruit
In fruit_policy.rb I got this:
class Scope < Scope
def resolve
if user.is_near_equator
scope.where(class: Tropical)
else
scope.where(class: Temperate)
end
end
end
The class check above gives me:
NameError (uninitialized constant Fruit::Tropical...
_________________________________________^^^^^^^^^
Is it possible to check the class within a scope in a policy? If so, how?

When you implemented the subclasses with STI and followed Rails naming conventions for database columns then you can scope the query to a subclass by filtering on the type columns with the desired class name as a string:
def resolve
if user.is_near_equator
scope.where(type: 'Fruit::Tropical')
else
scope.where(type: 'Fruit::Temperate')
end
end

Related

Calling models with _ us controllers

I have a table in the model called pg_search_documents, how do I work with it in the controllers?
I'm trying like this:
def show
#search = PgSearchDocument.find(params[:content])
end
But the so-called "PgSearchDocument" seems to be wrong.
You need to make sure you have a model declared in your app. If you have not done so, create the following file:
app/models/pg_search_document.rb
class PgSearchDocument < ActiveRecord::Base
end
In Rails 5 you would use:
class PgSearchDocument < ApplicationRecord
end
Please note the following naming conventions in Rails:
Database table name is plural snake case: pg_search_documents
Model filename is singular snake case: pg_search_document.rb
Model class name is singular camel case: PgSearchDocument

Rails - Create instance of a model from within another model

I have an application I'm building where I need one model to create instances of another model. I want every Car to have 4 tires.
Car model
class Car < ActiveRecord::Base
has_many :tires
after_create :make_tires
def make_tires
4.times { Tire.create(car: self.id) }
end
end
Tire model
class Tire < ActiveRecord::Base
belongs_to :car
end
However, inside of make_tires there is an error that there is no activerecord method for create or new if I try it for Tire. When I inspect Tire it doesn't have those methods.
How can I remedy this?
The error is this: undefined method 'create' for ActiveRecord::AttributeMethods::Serialization::Tire::Module
I have tested two environments: Testing and Development and they both fail for the same error.
It is a name conflict. Sit down and relax while I explain.
Solution with explanation:
In Ruby classes are just instances of class Class (which is a subclass of class Module). Instances of Module (including instances of Class) are quite weird objects, especially weird is their connection with ruby constants. You can create a new class at any point using standard ruby notation:
my_class = Class.new { attr_accessor :a }
instance = my_class.new
instance.a = 3
insatnce.a #=>
instance.class.name #=> nil
Well, our class has no name. It is just an anonymous class. How do classes get their name? By assigning it to a constant (for the first time):
MyClass = my_class
my_class.name #=> 'MyClass'
When you define class using a class keyword:
class MyClass
...
end
You just create a new instance of Class and assign it to a constant. Because of that, Ruby compiler seeing a constant has no idea whether it is a class or a number under it - it has to make a full search for that constant.
The logic behind finding a constant is quite complex and depends on the current nesting. Your case is quite simple (as there is no nesting), so ruby will try to find Tire class inside your class first and when failed it's subclasses and included modules.
Your problem is that your class inherits from ActiveRecord::Base (which is correct), which includes ActiveRecord::AttributeMethods::Serialization module, which defines Tire constant already. Hence, ruby will use this constant instead, as this is the best match for that name in given context.
To fix it, you must tell the compiler not to look within the current class but directly in the "top namespace" (which in ruby is Object. Seriously, try Object.constants) - you can do that using :: in front of your constant, like ::Tire.
Note: even though it works, this issue is a first warning for you that your code starts to smell. You should look after this ActiveRecord::AttributeMethods::Serialization::Tire::Module thingy as it seems you will encounter it more than once in the future.
Other stuff:
You can simplify your method slightly:
def make_tires
4.times { tires.create }
end
At that point you might encounter some error you had initially. If you do, then please find what is going on with that Tire::Module thing. If you don't care about the smell:
has_many :tires, class_name: '::Tire'
I'm not sure what's causing the exception you are seeing but you have a number of issues. First, you need to pass in a car instance instead of the id in make_tires. Like this:
def make_tires
4.times { Tire.create(car: self) }
end
You also need to have attr_accessible :car in the Tire model. Like this:
class Tire < ActiveRecord::Base
belongs_to :car
attr_accessible :car
end

Rails model inherit business logic only

In a rails 4 app I have two models with the same schema but different tables. They are FeedEntry and HistoricalFeedEntry. I would like HistoricalFeedEntry to only inherit functionality I add to FeedEntry. The models look like so:
class FeedEntry < ActiveRecord::Base
def self.published_at_cutoff
# date cutoff before which entries are old
Time.now - 1*7*24*60*60
end
end
class HistoricalFeedEntry < FeedEntry
end
When I enter the rails console and do HistoricalFeedEntry.all I get all the results from the FeedEntry table. What I would like is to only inherit published_at_cutoff (and other methods defined by me). Thanks.
You can use a mix-in for such a thing. Create a module which contains the business logic, class methods and instance methods. And "include" it in each of the models. Something like below:
class FeedEntry < ActiveRecord::Base
include FeedEntryBusinessLogic
end
module FeedEntryBusinessLogic
def self.published_at_cutoff
# date cutoff before which entries are old
Time.now - 1*7*24*60*60
end
end
class HistoricalFeedEntry < ActiveRecord::Base
include FeedEntryBusinessLogic
end
Because you are using Rails 4, you can use Concerns ( which are similar ). Read:
https://signalvnoise.com/posts/3372-put-chubby-models-on-a-diet-with-concerns

Rails gets wrong class when two classes have the same name

In my Rails I have the following models:
A STI sub-class
class Subscription::Discount < Subscription
def self.new_with_url
...
end
end
and another model class (doing completely different things, this is a STI base class)
class Discount < ActiveRecord::Base
end
So in my controller, I uses Subscription::Discount when I create users:
#user.subscription = ::Subscription::Discount.new_with_url()
However it complains: undefined method 'new_with_url' for #<Class:0x007fbb499c6740>
I think Rails is not calling the right class with new_with_url. On top of that I am not sure what #<Class:0x007fbb499c6740> is. So, two questions:
Without renaming any model, how can I reference Subscription::Discount properly?
Why is the error message saying #<Class:0x007fbb499c6740>, I can understand if it is Discount instead of that anonymous class.
EDIT:
Here are all the relevant models:
app/model/discount.rb
app/model/coffee_discount.rb (CoffeeDiscount < Discount)
app/model/subscription.rb
app/model/subscription/discount.rb (Subscription::Discount < Subscription)
The method is named create_with_url but you're calling new_with_url.
Fix the method name.

Rails -- use type column without STI?

I want to use a column called type without invoking Single Table Inheritance (STI) - I just want type to be a normal column that holds a String.
How can I do this without having Rails expecting me to have single table inheritance and throwing an exception of The single-table inheritance mechanism failed to locate the subclass...This error is raised because the column 'type' is reserved for storing the class in case of inheritance.?
Any ideas on how to do this?
In Rails 3.1 set_inheritance_column is deprecated, also you can just use nil as a name, like this:
class Pancakes < ActiveRecord::Base
self.inheritance_column = nil
#...
end
You can override the STI column name using set_inheritance_column:
class Pancakes < ActiveRecord::Base
set_inheritance_column 'something_you_will_not_use'
#...
end
So pick some column name that you won't use for anything and feed that to set_inheritance_column.
In newer versions of Rails you'd set inheritance_column to nil:
class Pancakes < ActiveRecord::Base
self.inheritance_column = nil
#...
end
I know this question is rather old and this deviates a bit from the question you are asking, but what I always do whenever I feel the urge to name a column type or something_type is I search for a synonym of type and use that instead:
Here are a couple alternatives: kind, sort, variety, category, set, genre, species, order etc.
Rails 4.x
I encountered the problem in a Rails 4 app, but in Rails 4 the set_inheritance_column method does not exist at all so you can't use it.
The solution that worked for me was to disable the single table inheritance by overriding ActiveRecord’s inheritance_column method, like this:
class MyModel < ActiveRecord::Base
private
def self.inheritance_column
nil
end
end
Hope it helps!
If you want to do this for all models, you can stick this in an initializer.
ActiveSupport.on_load(:active_record) do
class ::ActiveRecord::Base
# disable STI to allow columns named "type"
self.inheritance_column = :_type_disabled
end
end

Resources