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

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.

Related

Rails common method for updating a database field

I am new to rails and I have a task to write a common method that will update a specific database field with a given value. And I should be able to invoke the method from anywhere in the app.(I understand about the security flaw and so on.. But I was asked to do it anyway) In my application controller I tried
def update_my_model_status(model,id,field, value)
#model = model.find(id)
#model.update(field: value)
end
Of course this doesn't work.. How to achieve this? What is the right way to do this? And if it is possible how to pass a model as an argument to a method?
If you're using Rails, why not use Rails?
Compare update_all:
MyModel.where(id: 1).update_all(banned: true)
or maybe update_attribute:
my_model.update_attribute(:banned, true)
to:
update_my_model_status(MyModel, 1, :banned, true)
Notice how, despite being shorter, the first two approaches are significantly more expressive than the last - it is much more obvious what is happening. Not only that, but they are immediately more familiar to any Rails developer off the street, while the custom one has a learning curve. This, combined with the added code from the unnecessary method adds to the maintenance cost of the application. Additionally, the Rails methods are well tested and documented - are you planning to write that, too? Finally, the Rails methods are better thought out - for example, your prototype naively uses attribute validations, but does not check them (which could result in unexpected behavior) and makes more SQL queries than it needs to. It's fine to write custom methods, but let's not write arbitrary wrappers around perfectly fine Rails methods...
Try this:
def update_my_model_status(model,id,field, value)
#model_var = model.capitalize.constantize.find(id)
#model_var.update_attributes(field: value)
end
Instead of just using update you should use update_attributes:
def update_my_model_status(model,id,field, value)
#model_var = model.find(id)
#model.update_attributes(field: value)
end
http://api.rubyonrails.org/classes/ActiveRecord/Persistence.html#method-i-update

rails scope cannot generate correct sql

I added a variable in config/application.rb:
config.available_account_types = %w(SystemAccount CashAccount DemandAccount LiabilityAccount CreditCardAccount)
And generated some scopes in model account.rb:
for t in Mypurse::Application.config.available_account_types
scope t.underscore.pluralize.to_sym, -> {where(type: t)}
end
But when I try all of them, Account.system_accounts, Account.cash_accounts, etc, I got this sql for every account type:
where type = 'CreditCardAccount'
That is, all of the generated scope are pointed to the {where(type: 'CreditCardACcount')}
I don't know why.
here is the source file:
https://github.com/chylli/mypurse/blob/add_cash_demand_liability_credit_card/config/application.rb
https://github.com/chylli/mypurse/blob/add_cash_demand_liability_credit_card/app/models/account.rb
I think this is caused because a scope is given a Proc which is only executed when called, and so t will always be the last element of the loop.
A solution is to define methods instead of scopes (which work exactly the same) :
MyPurs::Application.config.available_account_types.each do |account_type|
define_singleton_method(account_type.underscore.pluralize.to_sym) do
where(type: "#{account_type}")
end
end
But since this does not declare a proc, this should work as expected.
Also the for .. in is rarely used in ruby, I personally prefer to use the more idiomatic .each (but of course you are free to use whatever you want, programmer happiness is key in ruby :) :)
Now as an aside, while meta-programming is really cool, you should really ask yourself if just listing the scopes is not way more readable. I understand: meta-programming is more DRY, but personally, in most cases where I did this, I reverted to the explicit definitions because of readability.
I am not sure why you have defined 'config.available_account_types', as this is business logic. this should belong to Account modal. so I would do something like this
class Account < ActiveRecord::Base
ACCOUNT_TYPES = %w(SystemAccount CashAccount DemandAccount LiabilityAccount CreditCardAccount)
ACCOUNT_TYPES.each do |acccount_type|
define_singleton_method(account_type.underscore.pluralize.to_sym) do
where(type: "#{account_type}")
end
end
end

when should I used Lambda or Anonymous method?

I understand the working of block , proc and lambda experession but what i dont understand that when to use anonymous method in code.
What is the problem which Anonymous solves ?
One very common usage of lambdas is for lazy-loading of (and passing parameters to) ActiveRecord relations in scopes:
class Post < ActiveRecord::Base
scope :recent, lambda { |today| where('published_at >= ?', today) }
end
(from here)
In general though, these closure methods are a concise way of operating on (e.g.) a collection of data all at once, or storing code as data to be passed around to other functions.
One of the reasons I use lambdas in this way in Ruby is when I need a closure to capture stuff from the surrounding scope, which for example in smaller scripts I sometimes find more convenient than passing things around as arguments. Other people (ab)use top-level instance variables for that, but I don't like that much.
Update as requested: Here's a little example:
shift_timestamp = -> ts do
t = Time.parse(ts.gsub(',','.')) + options[:time]
"#{t.strftime("%H:%M:%S")},#{t.usec.to_s[0..2]}"
end
This is for a little tool I wrote for shifting subtitles. It's a short script and writing it this way allowed me to close over options and access it inside the lambda without having to pass it in. As I said, nothing funky, but for small scripts I sometimes like to do this (where "this" is parsing options, writing 1 or 2 lambdas that use those options, use the lambdas instead of methods later on).

Empty Scope with 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

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