Empty Scope with Ruby on Rails - ruby-on-rails

Following Problem:
I need something like an empty scope. Which means that this scope is emtpy, but responds to all methods a scope usually responds to.
I'm currently using a little dirty hack. I simply supply "1=0" as conditions. I find this realy ugly, since it hits the database. Simply returning an empty array won't work, since the result must respond to the scoped methods.
Is there a better existing solution for this or will I need to code this myself?
Maybe some example code could help explain what i need:
class User < ActiveRecord::Base
named_scope :admins, :conditions => {:admin => true }
named_scope :none_dirty, :conditions => "1=0" # this scope is always empty
def none_broken
[]
end
def self.sum_score # okay, a bit simple, but a method like this should work!
total = 0
self.all.each do |user|
total += user.score
end
return total
end
end
User.admin.sum_score # the score i want to know
User.none_drity.sum_score # works, but hits the db
User.none_broken.sum_score # ...error, since it doesn't respond to sum_score

Rails 4 introduces the none scope.
It is to be used in instances where you have a method which returns a relation, but there is a condition in which you do not want the database to be queried.
If you want a scope to return an unaltered scope use all:
No longer will a call to Model.all execute a query immediately and return an array of records. In Rails 4, calls to Model.all is equivalent to now deprecated Model.scoped. This means that more relations can be chained to Model.all and the result will be lazily evaluated.

User.where('false')
returns an ActiveRecord::Relation with zero elements, that is a chain-able scope that won't hit the database until you actually try to access one of its elements. This is similar to PhilT's solution with ('1=0') but a little more elegant.

Sorry User.scoped is not what you want. As commented this returns everything. Should have paid more attention to the question.
I've seen where('1 = 0') suggested before and Rails should probably cache it as well.
Also, where('1 = 0') won't hit the database until you do .all, .each, or one of the calculations methods.

I thing you need User.scoped({})

How about User.where(id: nil) ?
Or User.where(_id: nil) for mongoid.

The thing you are looking for does not exist. You could implement something like this by monky patching the find method. Yet, this would be an overkill, so I recomend keeping this unless it's performance critical.

Looking at your example code indicates you may not know about aggregated queries in SQL which are exposed as calculations methods in Rails:
User.sum(:score) will give you the sum of all users' scores
Take a look at Rails Guides for more info:
http://guides.rubyonrails.org/active_record_querying.html#sum

Related

Ruby on Rails #find_each and #each. is this bad practice?

I know that find_each has been designed to consume smaller memory than each.
I found some code that other people wrote long ago. and I think that it's wrong.
Think about this codes.
users = User.where(:active => false) # What this line does actually? Nothing?
users.find_each do |user|
# update or do something..
user.update(:do_something => "yes")
end
in this case, It will store all user objects to the users variable. so we already populated the full amount of memory space. There is no point using find_each later on.
Am I correct?
so in other words, If you want to use find_each, you always need to use it with ActiveRecord::Relation object. Like this.
User.where(:active => false).find_each do |user|
# do something...
end
What do you think guys?
Update
in users = User.where(:active => false) line,
Some developer insists that rails never execute query unless we don't do anything with that variable.
What if we have a class with initialize method that has query?
class Test
def initialize
#users = User.where(:active => true)
end
def do_something
#user.find_each do |user|
# do something really..
end
end
end
If we call Test.new, what would happen? Nothing will happen?
users = User.where(:active => false) doesn't run a query against the database and it doesn't return an array with all inactive users. Instead, where returns an ActiveRecord::Relation. Such a relation basically describes a database query that hasn't run yet. The defined query is only run against the database when the actual records are needed. This happens for example when you run one of the following methods on that relation: find, to_a, count, each, and many others.
That means the change you did isn't a huge improvement, because it doesn't change went and how the database is queried.
But IMHO that your code is still slightly better because when you do not plan to reuse the relation then why assign it to a variable in the first place.
users = User.where(:active => false)
users.find_each do |user|
User.where(:active => false).find_each do |user|
Those do the same thing.
The only difference is the first one stores the ActiveRecord::Relation object in users before calling #find_each on it.
This isn't a Rails thing, it applies to all of Ruby. It's method chaining common to most object-oriented languages.
array = Call.some_method
array.each{ |item| do_something(item) }
Call.some_method.each{ |item| do_something(item) }
Again, same thing. The only difference is in the first the intermediate array will persist, whereas in the second the array will be built and then eventually deallocated.
If we call Test.new, what would happen? Nothing will happen?
Exactly. Rails will make an ActiveRecord::Relation and it will defer actually contacting the database until you actually do a query.
This lets you chain queries together.
#inactive_users = User.where(active: false).order(name: :asc)
Later you can to the query
# Inactive users whose favorite color is green ordered by name.
#inactive_users.where(favorite_color: :green).find_each do |user|
...
end
No query is made until find_each is called.
In general, pass around relations rather than arrays of records. Relations are more flexible and if it's never used there's no cost.
find_each is special in that it works in batches to avoid consuming too much memory on large tables.
A common mistake is to write this:
User.where(:active => false).each do |user|
Or worse:
User.all.each do |user|
Calling each on an ActiveRecord::Relation will pull all the results into memory before iterating. This is bad for large tables.
find_each will load the results in batches of 1000 to avoid using too much memory. It hides this batching from you.
There are other methods which work in batches, see ActiveRecord::Batches.
For more see the Rails Style Guide and use rubocop-rails to scan your code for issues and make suggestions and corrections.

Remove element from ActiveRecord_Relation in rails

How can i remove the last element from an ActiveRecord_Relation in rails?
e.g. if I set:
#drivers = Driver.all
I can add a another Driver object called #new_driver to #drivers by doing:
#drivers << #new_driver
But how can I remove an object from #drivers?
The delete method doesn't seem to work, i.e.
#drivers.delete(0)
You can use the reject! method, this will remove the object from the collection without affecting the db
for example:
driver_to_delete = #driver.first # you need the object that you want removed
#drivers.reject!{|driver| driver == driver_to_delete}
Very late too, but I arrived here looking for a fast answer and finished by thinking by myself ;)
Just to clarify about the different answers and the Rails 6.1 comment on accepted answer:
The OP wanted to remove one entry from a query, but NOT remove it from database, so any answer with delete or destroy is just wrong (this WILL delete data from your database !!).
In Ruby (and therefore Rails) convention, shebang methods (ending with !) tend to alter the given parameter. So reject! would imply modifying the source list ... but an ActiveRecord_Relation is basically just a query, NOT an array of entries !
So you'd have 2 options:
Write your query differently to specifically say you don't want some id:
#drivers.where.not(id: #driver_to_remove) # This still is an ActiveRecord_Relation
Use reject (NO shebang) on your query to transform it into an Array and "manually" remove the entry you don't want:
#drivers.reject{ |driver| driver == #driver_to_remove}
# The `reject` forces the execution of the query in DB and returns an Array)
On a performance point of view, I would personally recommend the first solution as it would be just a little more complex against the DB where the latter implies looping on the whole (eventually large) array.
Late to the question, but just had the same issue and hope this helps someone else.
reject!did not work for ActiveRecord_Relation in Rails 4.2
drop(1) was the solution
In this case #drivers.drop(0) would work to drop the first element of the relation
Since its an array of objects, have you tried to write something like #drivers.delete(#new_driver) or #drivers.delete(id: #new_driver.id) ?
This is the documentation you need:
#group.avatars << Avatar.new
#group.avatars.delete(#group.avatars.last)
--
.destroy
The problem you've got is you're trying to use collection methods on a non-collection object. You'll need to use the .destroy ActiveRecord method to get rid of the record from the database (and consequently the collection):
#drivers = Driver.all
#drivers.last.destroy
--
Scope
.delete will remove the record from the DB
If you want to pull specific elements from the db to populate the #drivers object, you'll need to use a scope:
#app/models/driver.rb
Class Driver < ActiveRecord::Base
scope :your_scope, -> { where column: "value" }
end
This will allow you to call:
#app/controllers/drivers_controller.rb
def index
#drivers = Driver.your_scope
end
I think you're getting the MVC programming pattern confused - data manipulation is meant to happen in the model, not the controller
As stated above, reject! doesn't work in Rails 4.2, but delete does, so #drivers.delete(#new_driver) works, and more generally:
#drivers.delete(Driver.where(your condition))

Rails: What's the difference between lambda, scope and class method? What's best practice?

The code snippet has three methods: lambda, scope and class method.
All of them returns the same results.
Questions:
Is there any best practice in Ruby/Rails when it is preferred to use one over the other ?
In what cases would you use lambda, scope or class method ( best practices ).
class Cars < ActiveRecord::Base
attr_accessible :manufacturer, :price, :used
#one
scope :used_and_cheap_lambda, lambda { where('used = ?', true ).where('price >= ?',30000) }
#two
scope :used_and_cheap_scope, where('used = ?', true ).where('price >= ?',30000)
#three
def self.used_and_cheap_class
where('used = ?', true ).where('price >= ?',30000)
end
end
Cars.used_and_cheap_lambda.count
=> #24
Cars.used_and_cheap_class.count
=> #24
Cars.used_and_cheap_scope.count
=> #24
It's best to avoid using option 2. That code gets run immediately when your Rails app loads which is bad since it will always return the same value for any Time argument you use in it. That's because it isn't reevaluated every time it's called.
Option 1, as pointed out by musicnerd47, are lazy loaded and it is advisable that you pass lambdas to scopes in Rails 4 rather than doing option 2 since they are reevaluated every time called so they will return updated values.
So the only options would be 1 and 3. This is usually a matter of style that your team adheres to. In our company, we use option 1 when the code we pass to it is going to be an ActiveRecord query and we want it to output a query that can be chained. This is to ensure that an ActiveRecord::Relation object is returned every time we do our queries for multiple records. That would mean that they are always going to be chainable with other ActiveRecord::Relation methods and our other defined scopes.
We use option 3 if it's for behavior that doesn't need to be chained with other scopes.
Here's a good read on the matter of scopes and class_methods where he goes into detail and provides examples on the difference between scopes and class methods.
http://blog.plataformatec.com.br/2013/02/active-record-scopes-vs-class-methods/
Starting in Rails 4 you have to use a lambda - in general it is a better practice because it is lazily loaded and prevents a lot of traps, especially when dealing with dates and times.
I think for simple scopes that deal with a single where call or something, using scope is okay. When it is more complex, then moving to a class method is better (for example, when you need to be calling other methods or setting local variables before you return the scope).
I would use the lambda. The function you're describing is sufficiently simple. Using the lambda initializes lazily as well. I direct you here to the rails style guide.
Unfortunately there is no golden rule. Scopes are designed for this exact application. When the application of logic comfortably fits into a scope, I think that's the best bet. When things start to get too complex, it's usually best to move the logic into a class method.

rails/activerecord search eager loaded associations

I have a simple find statement as such:
m = MyModel.find(1, :include => :my_children)
With m.mychildren being an Array; is there anyway to find a particular record from within the array without having to iterate over the entire thing. If I do mychildren.find(1), a new DB query is issues, which doesn't make sense, since they are all loaded already
It looks like there's a little Rails magic going on here. Where Enumerable#find is being overridden by ActiveRecord::Base#find on methods created for associations.
On the upside Enumerable#find is aliased to Enumerable#detect.
Unfortunately Enumerable#find/Enumerable#detect have significantly different syntax from ActiveRecord::Base#find.
So you can't just do mychildren.find(1), instead you've got to do mychildren.detect{|c| c.id == 1} if you want to avoid hitting the database again. You may also want to consider extending Array for a more DRY way of doing this.
class Array
def id_find id
self.detect{|element| element.id == id}
end
end
I'm not quite sure what your asking, but have you tried select:
m.mychildren.select{ |child| child == <<some_statement>> }
This won't hit the database assuming you've used the :include option as you stated in your question.
Alternatively, if you know the number of the child you want, you should be able to just use
m.mychildren[1]

Allow the user to pick a named scope via GET params

In my posts model, I have a named scope:
named_scope :random, :order => "Random()"
I'd like to give users the ability to get posts in a random order by sending a GET request with params[:scope] = 'random'.
Short of eval("Post.#{params[:scope]}"), how can I do this?
I would suggest my very awesome acts_as_filter plugin designed for user-driven filtering of results via named_scopes.
http://github.com/tobyhede/acts_as_filter/tree/master
Eval is fine to use - but make sure you validate against accepted/expected values (I often just plug some values into an array and test accepted_values.include?(parameter))
eval is a pretty bad idea. However, #send is perfect for this - it's inherently safer, and faster than eval (as I understand it).
Product.send(params[:scope])
That should do it :)
I came across it in a search. searchlogic is perfect for this.
I would stay away from eval since you're dealing with data that comes from the user. Maybe just use a simple case statement? This way you'll be able to validate what the data they're giving you.
For the example you give, I'd be explicit, and chain scopes together to build the query you want:
scope = Post
scope = scope.random if params[:scope] == 'random'
#posts = scope.find(:all, ...) # or paginate or whatever you need to do
If params[:scope] isn't 'random', this is the same as calling Post.find(), otherwise it's doing Post.random.find()
From one of the other answers, it looks like find_by_filter would do pretty much the same thing for you.
Using this pattern, you can also combine multiple scopes into the query if you needed to support things that weren't mutually exclusive
e.g.
scope = scope.only_monsters if params[:just_monsters] == 1
scope = scope.limit(params[:limit].to_i) unless params[:limit].to_i.zero?
So GETting /posts?scope=random&just_monsters=1&limit=5 will give you:
Post.random.just_monsters.limit(5).find(:all, ...)

Resources