Ruby: Get result of previous method in current method - ruby-on-rails

The following illustrates my problem:
User.all.testing
def self.testing
v = self
v.group_by { |user| user.username = 'username' }
end
leads to:
undefined method `group_by' for Class:0x8ca0558
However:
User.all.group_by { |user| user.username = 'username' }
works.

As mentioned in the comments, I can't explain where this group_by method that accepts a parameter is coming from, but to make your testing method work the same as User.all.group_by(username: 'username') write it as
def self.testing
all.group_by(username: 'username')
end
The use of all will create an ActiveRecord relation for you. Note that even though it says "all", if you do have any other scopes in effect these will be carried across. e.g. it would behave correctly for User.where(some: value).testing. If you call testing directly without any other scopes established i.e. User.testing then then default scope will be used.

self is a kind of ActiveRecord::Base object, and thus doesn't have a group_by method. group_by is an Enumerable method. Perhaps you mean group, which is implemented in ActiveRecord::QueryMethods?

self.testing means this is a class method and can be used like User.testing NOT User.all.testing because User.all returns active record relation.
Solution: Create scope in your model
class User < ApplicationRecord
scope :testing, -> (username) { group_by(username: username) }
end
Now you can use User.all.testing

Related

Rails active record model method chaining and getting the initial input

This feels like it should be very simple but am unsure the correct syntax. This is me playing and learning, so this isn't pressing.
I would like to write a custom method on a model that performs an action and takes the input from the method chain instead of require you to set the input arguments in the method call.
Given that we have the following simple class;
class Comment < ApplicationRecord
scope :for_user, ->(u) { where(user_id: u.id) }
def self.do_thing
# How do I get the results from the chain?
# For example how do I get the IDs?
end
end
So I'd like to be able to do this;
Comments.for_user(current_user).do_thing
And have the do_thing method know what the results of Comments.for_user(current_user)
Now obviously I could make do_thing have method arguments and just go that route, but I'm playing and learning about method chaining..
Thanks for your input.
Class Comment < ApplicationRecord
scope :for_user, ->(u) { where(user_id: u.id) }
def self.do_thing
# How do I get the results from the chain?
# For example how do I get the IDs?
self.ids
end
end
Use self. The way scopes work is that they return an ActiveRecord::Relation object that proxies method calls back to the model class.
Although in this case the method will actually break chaining since it returns an array and not self or an ActiveRecord::Relation.

Rails - apply an array of scopes

I have a class that takes an array of scopes and applies them iteratively:
class AssignableLearningObjectives::Collector
def initialize(user:, only_self_assignable: false, scopes: [])
#user = user
#only_self_assignable = only_self_assignable
#scopes = scopes
end
.....
def available_objectives
objectives = assignable_objectives.or(manager_assigned_objectives).or(global_objectives).distinct
return objectives unless scopes.any?
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
end
My issue is with
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
Is there a better way of doing this? I was hoping for a rails method apply_scopes or something like that, however can't find anything like that.
My concern is the scopes are sent from the controller, and it is possible for the user to submit a request with a scope of 'destroy_all' or something equally fun.
Is there an easy way for me to let rails handle this? Or will I need to manually check each scope before I apply it to the collection?
Thanks in advance
EDIT:
I'm happy to validate each scope individually if I have to, but even that's causing issues. There is a method in rails which was dropped in 3.0.9 which I could use, Model.scopes :
https://apidock.com/rails/v3.0.9/ActiveRecord/NamedScope/ClassMethods/scopes
however that's deprecated. Is there any method I can call on a class to list its scopes? I can't believe the feature was there in rails 3 and removed completely...
From the fine guide:
14 Scopes
[...]
To define a simple scope, we use the scope method inside the class, passing the query that we'd like to run when this scope is called:
class Article < ApplicationRecord
scope :published, -> { where(published: true) }
end
This is exactly the same as defining a class method, and which you use is a matter of personal preference:
class Article < ApplicationRecord
def self.published
where(published: true)
end
end
So scope is mostly just a fancy way of creating a class method that is supposed to have certain behavior (i.e. return a relation) and any class method method that returns a relation is a scope. scope used to be something special but now they're just class methods and all the class methods are copied to relations to support chaining.
There is no way to know if method Model.m is a "real" scope that will return a relation or some random class method without running it and checking what it returns or manually examining its source code. The scopes method you seek is gone and will never come back.
You could try to blacklist every class method that you know is bad; this way lies bugs and madness.
The only sane option is to whitelist every class method that you know is good and is something that you want users to be able to call. Then you should filter the scopes array up in the controller and inside AssignableLearningObjectives::Collector. I'd check in both places because you could have different criteria for what is allowed depending on what information is available and what path you're taking through the code; slightly less DRY I suppose but efficiency and robustness aren't friends.
You could apply the scope whitelist in the AssignableLearningObjectives::Collector constructor or in available_objectives.
If you want something prettier than:
scopes.each{ |scope| objectives = objectives.send(scope) }
objectives
then you could use inject:
def available_objectives
objectives = assignable_objectives....
scopes.inject(objectives) { |objectives, scope| objectives.send(scope) }
end

What is appropriate scope to replace this method?

I have a dashboard_controller that I am using to manage users. Here is that controller:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
if current_user.admin?
#users = current_user.get_organization_users
else
flash[:notice] = "Unauthorized Page View"
redirect_to(tasks_url)
end
end
Note I am using #users = current_user.get_organization_users. Here is the get_organization_users method in my user model...
def get_organization_users
self.organization.users
end
How would I replace this with a scope? I tried...
scope :organization_users, -> { self.organization.users }
...but no worky. Any help appreciated.
A scope is used to add a class method to your model. But you're trying to call the method on an instance. So, in this case, the instance method makes sense.
However, if you want to create a scope, pass in the user_id as a parameter to the scope.
scope :organization_users(user_id), -> { find(user_id).organization.users }
Internally, Active record converts scopes into class methods.
That would mean that you can't replace the instance method get_organization_users with a scope and expect to call it on current_user, an instance of the class.
What you could do is create a scope and pass an argument (most probably the user id) to it, then call that scope directly on the user class.
I could give an example if you wish, but I think this approach is much longer than the desired one.
I am not sure about your question. You want to fetch all users that belongs to the organization associated with the current user, right? In such case you should just call current_user.organization.users.
Scope is used for filtering records in the current model and not for getting objects that are in the relation. You can read about it in the official documentation: http://guides.rubyonrails.org/active_record_querying.html#scopes.

How to query Rails / ActiveRecord model based on custom model method?

Disclaimer: I'm relatively new to rails.
I have a custom method in my model that I'd like to query on. The method, called 'active?', returns a boolean. What I'd really like to do is create an ActiveRecord query of the following form:
Users.where(:active => true)
Naturally, I get a "column does not exist" when I run the above as-is, so my question is as follows:
How do I do the equivalent of the above, but for a custom method on the model rather than an actual DB column?
Instead of using the active? method, you would have a scope to help find items that match.
Something like this...
def self.active
joins(:parent_table).where(:archived => false).where("? BETWEEN parent_table.start_date AND parent_table.end_date ", Time.now)
end
And, you should be able to do this
def active?
User.active.exists?(self)
end
If you would like to reuse this scope for the instance test.
An easy way to do this would be by using the select method with your exiting model method.
Users.select{|u| u.active}
This will return an array so you won't be able to use Active Record Query methods on it. To return the results as an ActiveRecord_Relation object, you can use the where function to query instances that have matching ids:
class User < ActiveRecord::Base
def self.active
active_array = self.select{|r| r.active?}
active_relation = self.where(id: active_array.map(&:id))
return active_relation
end
end

rails, how to pass self in function

message and user. my message belongs_to user and user has_many messages.
in one of my views, i call something like
current_user.home_messages?
and in my user model, i have...
def home_messages?
Message.any_messages_for
end
and lastly in my message model, i have
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
ive been trying to get the current_users id in my message model. i could pass in current_user as a parameter from my view on top but since im doing
current_user.home_messages?
i thought it would be better if i used self. but how do i go about referring to it correctly?
thank you.
You could use a lambda. In your Message model:
scope :any_messages_for, lambda {|user| where('user_id = ?', user.id)}
This would work like so:
Message.any_messages_for(current_user)
And you could add a method to your user model to return true if any messages are found. In this case you use an instance method and pass in the instance as self:
def home_messages?
return true if Message.any_messages_for(self)
end
But really, I'd just do something like this in the User model without having to write any of the above. This uses a Rails method that is created when declaring :has_many and :belongs_to associations:
def home_messages?
return true if self.messages.any?
end
You can do either of the following
def self.any_messages_for(id) #This is a class method
Message.where("to_id = ?", id).exists?
end
to call above method you have to do
User.any_messages_for(current_user.id) #I am assuming any_messages_for is in `User` Model
OR
def any_messages_for #This is a instance method
Message.where("to_id = ?", self.id).exists?
end
to call above method you have to do
current_user.any_messages_for
This stuff in your Message class doesn't make a lot of sense:
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
The scope macro defines a class method on its own and there should be another argument to it as well; also, scopes are meant to define, more or less, a canned set of query parameters so your any_messages_for method isn't very scopeish; I think you should get rid of scope :any_messages_for.
In your any_messages_for class method, self will be the class itself so self.id won't be a user ID and so it won't be useful as a placeholder value in your where.
You should have something more like this in Message:
def self.any_messages_for(user)
where('to_id = ?', user.id).exists?
# or exists?(:to_id => user.id)
end
And then in User:
def home_messages?
Message.any_messages_for(self)
end
Once all that's sorted out, you can say current_user.home_messages?.

Resources