I've recently started an internship. My employer uses ruby on rails, and I frequently encounter new syntax that I need to look up to understand. I've googled around for a good explanation of named_scope, but what I've found so far is mostly blog posts giving high praise for it, rather a straight definition or introduction.
What exactly is named_scope (now simply called scope) in ruby on rails?
A scope is a subset of a collection. Sounds complicated? It isn't. Imagine this:
You have Users. Now, some of those Users are subscribed to your newsletter. You marked those who receive a newsletter by adding a field to the Users Database (user.subscribed_to_newsletter = true). Naturally, you sometimes want to get those Users who are subscribed to your newsletter.
You could, of course, always do this:
User.where(subscribed_to_newsletter: true).each do #something
Instead of always writing this you could, however, do something like this.
#File: users.rb
class User < ActiveRecord::Base
scope :newsletter, where(subscribed_to_newsletter: true)
#yada yada
end
If you're using Rails 4 or newer, do this instead:
#File: users.rb
class User < ActiveRecord::Base
scope :newsletter, -> { where(subscribed_to_newsletter: true) }
#yada yada
end
This allows you to access your subscribers by simply doing this:
User.newsletter.each do #something
This is a very simple example but in general scopes can be very powerful tools to easy your work.
Check out this link: API Description
scope in active record is like class methods but they return Relation object which means you can call another scope or active record querying method on it.
For example, if you have a Zombie model (zombies table) with below mentioned scope methods,
class Zombie
scope :rotting, -> { where(rotting: true) }
scope :fresh, -> { where('age < ?', 25) }
scope :recent, -> { order(created_at: :desc) }
end
And you call
Zombie.rotting.fresh.recent.limit(3)
It translates to the below in SQL,
select "zombies.*" from "zombies" where "zombies"."rotting" = 't' and (age<20) order by create_at desc limit 3
Example above is based on rails 4 syntax
The best way to understand about the details is to go to API Documentation.
You'll get the complete details and the ways we can use Scopes.
API Documentation of Scope
Imagine you have a model: Person.
Now imagine you :
want all the people in the world who have red hair.
want all the people in the world who play cricket
You could get those particular classes of people by using a scope!
Person.red_hair.cricket ## finds all people with red hair who play cricket
Person.red_hair ## finds all people with red hair
Person.cricket ## finds all people who play cricket.
Now that wasn't so hard was it?
Related
I have scopes defined on my Job model, and I want to use them when including jobs in an Active Record query, rather than writing out long-hand the conditions and ordering.
Here is the code I have that works but is very verbose:
#employees_and_jobs = supervisor.direct_reports.alphabetical \
.includes(:jobs) \
.where('jobs.active = true') \
.order('jobs.record_number asc, jobs.effective_date asc')
Here is the code I wish would work:
#employees_and_jobs = supervisor.direct_reports.alphabetical.includes(:jobs).active.sorted
The scopes direct_reports and alphabetical work, but the others (active and sorted) are interpreted as belonging to the Employee model, and give me an error. I want active and sorted to be interpreted as belonging to the Job model. How can I change the query to show that active and sorted are scopes for Job and not Employee?
The active and sorted scopes are of course defined on the Job model, and are done with an explicit reference to jobs (but of course that is not enough):
scope :sorted, -> { order('jobs.record_number asc, jobs.effective_date asc') }
scope :active, -> { where('jobs.active = true') }
(I didn't expect the explicit reference to jobs inside the scope to make it work, but I tried it just in case, and mention it in case someone else thinks it might work.)
How can I specify in my query that the final scopes are meant to apply to the included jobs, and not to the employees?
(I realize I can solve the problem with a default scope, but that can create new problems later, and I'm trying to avoid that. I would prefer the verbose version above over using a default scope.)
Similar (But Different) Questions
The answers to this question don't answer my question, but simply instead offer an alternative approach to dealing with the situation. (But I already have an alternative approach, given above. I have working code, but I'm trying to improve readability in a very particular way by using scopes on not just the main model but also the included model.)
I'm asking for a way to use scopes on the included model but those answers explain how to use a scope on the main model that in turn includes the other model. Two very different things. They are similar in that they both make the controller code simpler but the other approach makes the controller potentially less clear. It just moving all of the complexity into a single scope which would (in my case) be on the Employee model. I'm aiming to have have very specific scopes that I can compose together, which each have a very clear and clearly defined purpose.
scope is really just syntactic sugar for defining class methods. So like any other class method you can just call your scopes on the class which defines them:
class Job < ApplicationRecord
belongs_to :employee
scope :active, -> { where(active: true) }
scope :sorted, -> { order('jobs.record_number asc, jobs.effective_date asc') }
end
class Employee < ApplicationRecord
has_many :jobs
scope :with_active_jobs, ->{ include(:jobs).merge(Job.active).merge(Job.sorted) }
end
ActiveRecord::SpawnMethods#merge is probably one of the most underused features of AR. It lets you mash different scopes together programatically.
ActiveRecord is smart enough to specify the table with .where so there is not problem in using it in a join (.where('jobs.active = true') will also work fine too). Unfortunately .order is not as smart and .order(record_number: :asc, effective_date: :asc) will generate ORDER BY record_number ASC, effective_date ASC which will give an error.
There is no technical reason you have to do this in the model either. You can just do Employee.include(:jobs).merge(Job.active).merge(Job.sorted) or whatever in the controller if you want to compose the scopes there. But remember that controllers are really difficult to test compared to models.
I have a Model Bot and I would like to ensure that there is only one Bot object in my database. I also need to make sure it is persisted and not tampered with.
My original thought was to do this in a migration, one that would follow the :bots table migration. It would include a line that is something like:
Bot.all.size == 0 ? Bot.create! : nil
Maybe this would prevent the AR object from being messed with in future migrations?
BONUS: Would be awesome to be able to have instant and global access to this class object. I was thinking using a singleton module in my Bot class that way I can always reference Bot.instance and have access to that specific object.
USE CASE:
I have 4 types of users in my DB and this bot will be the facilitator to delivery role-specific messages to them through our in-app messaging feature.
The Class Bot will have a has_many association with BotMessage/bot_messages. On the bot_messages table will be an enum field for user_role.
Messages will be created by company admins and stored in these tables because we want them to be viewable at any time by looking at the "conversation" thread between the User and the Bot.
When it comes to only having 1 bot, it's just that. I have no need for an additional Bot object. Additionally, since there is only one object it would be nice to be able to have a way of explicitly targeting that object without having to run a query to find it.
For example, unlike User where there could be 1000 records and in order to find the specific one you would do something like #user = User.find_by_email('foo#bar.com'), doing something like that for the bot would be unnecessary since there is only one record to find. That is what lead me to believe having a singleton object may be worthwhile here, so whenever I need to pull up a message for a specific role, I could run Bot.instance.bot_messages.where(user_role: 1) or something similar
Based on your Use Case, I see no reason for Bot to be a model.
Let's say you have a role called cool_user and you want to get all the bot_messages for that role, you might do something like:
class Bot
class << self
def bot_messages(user_role)
BotMessage.send(user_role)
end
end
end
As a very thoughtful but potentially anonymous super code monkey notes in the comments, you could also do:
class Bot
def self.bot_messages(user_role)
BotMessage.send(user_role)
end
end
Which some folks might find more readable. IMO, it is a bit of an issue of personal preference.
In either case, you should be able to do
Bot.bot_messages(:cool_user)
Since, as stated in the docs,
Scopes based on the allowed values of the enum field will be provided as well.
So, I believe BotMessage, with the properly set enum, should respond to cool_user and return all the bot_messages for that role.
You may need to check the docs to get the syntax exactly right.
I believe this should also satisfy your BONUS requirement.
A proven solution would be to use an STI on User (with a user_type column)
class User < ApplicationRecord
...
end
class Bot < User
has_many :bot_messages, foreign_key: :user_id
end
Is it what you're looking for ?
I'm wanting to use UUIDs in an app I'm building and am running into a bit of a problem. Due to UUIDs (v4) not being sortable because they're randomly generated, I'm trying to override ActiveRecord::Base#first, but Rails isn't too pleased with that. It yells at me saying ArgumentError: You tried to define a scope named "first" on the model "Item", but Active Record already defined a class method with the same name. Do I have to use a different method if I want to sort and have it sort correctly?
Here's the sauce:
# lib/sortable_uuid.rb
module SortableUUID
def self.included(base)
base.class_eval do
scope :first, -> { order("created_at").first }
scope :last, -> { order("created_at DESC").first }
end
end
end
# app/models/item.rb
class Item < ActiveRecord::Base
include SortableUUID
end
Rails 4.2, Ruby 2.2.2
Reference:
http://blog.nakonieczny.it/posts/rails-support-for-uuid/
http://linhmtran168.github.io/blog/2014/03/17/postgres-uuid-in-rails/ ( Drawbacks section )
Rails 6 (currently in version 6.0.0rc1) comes to rescue with implicit_order_column!
To order by created_at and make .first, .last, .second etc. respect it is as simple as:
class ApplicationRecord < ActiveRecord::Base
self.implicit_order_column = :created_at
end
First of all, first and last aren't as simple as you seem to think they are: you're completely neglecting the limit argument that both of those methods support.
Secondly, scope is little more than a fancy way of adding class methods that are intended to return queries. Your scopes are abusing scope because they return single model instances rather than queries. You don't want to use scope at all, you're just trying to replace the first and last class methods so why don't you just override them? You'd need to override them properly though and that will require reading and understanding the Rails source so that you properly mimic what find_nth_with_limit does. You'd want to override second, third, ... and the rest of those silly methods while you're at it.
If you don't feel right about replace first and last (a good thing IMO), then you could add a default scope to order things as desired:
default_scope -> { order(:created_at) }
Of course, default scopes come with their own set of problems and sneaking things into the ORDER BY like this will probably force you into calling reorder any time you actually want to specify the ORDER BY; remember that multiple calls to order add new ordering conditions, they don't replace one that's already there.
Alternatively, if you're using Rails6+, you can use Markus's implicit_order_column solution to avoid all the problems that default scopes can cause.
I think you're going about this all wrong. Any time I see M.first I assume that something has been forgotten. Ordering things by id is pretty much useless so you should always manually specify the order you want before using methods like first and last.
After replacing id with uuid, I experienced some weirdness in the way associations were allocating foreign keys, and it wasn't that .last and .first, but instead because I simply forgot to add default: 'gen_random_uuid()' to one of the tables using a uuid. Once I fixed that, the problem was solved.
create_table :appointments, id: :uuid, default: 'gen_random_uuid()' do |t|
Sorry for the title I have some difficulties to explain my issue, and even more in english.
My use case is that:
I have one user who has_many teams, each team own one game.
I would like add a method (a scope I believe) in my model user who gets me all the games who are related with the user. I thought something like that:
scope :games, includes({:teams => [:game]})
This line don't work for some reason i don't know yet.
But if I succeed to do that I will get all the teams and all the games.
Whereas I would like only an array with the games.
I search the more elegant way to do that.
Thanks for reading :)
If you use Rails 4, then all scopes must be wrapped in lambdas.
scope :games, -> { includes({:teams => [:game]}) }
I am using Rails 3 and want whenever user will be created, then separate Data Base should be created for newly created user.
e.g if I have 13 migration in my application, so 13 tables should be created for newly created user.
How I can achieve this functionality?
Also check out the audio/video that goes along with James slides here http://www.confreaks.com/videos/889-railsconf2012-ten-things-you-didn-t-know-rails-could-do. Move forward to around 15:30 for the section on one user per database.
This is a really bad idea to handle many DB in Rails!
You can add a field, say, user_id in each of your tables that are needed to be separated and then apply default_scope to their respective models (or make an abstract model with default_scope via self.abstract_class = true and inherit your "shareable" models from it).
Your default_scope, as you might guess, should look like:
default_scope lambda { where(user_id: current_user_id) }
How to get the current user, you may ask?
Models can't access session, so you can make the following "hack" in order your scope to work:
#in ApplicationController
before_filter do
user_id = session[:user_id]
ActiveRecord::Base.class.send :define_method, :current_user_id, lambda { user_id }
end
I guess you got the idea.
This is best post i follow and solve my problem
http://7fff.com/2010/12/02/activerecord-dropcreate-database-run-migrations-outside-of-rails/