Rails; where to put query narrowing interface - ruby-on-rails

I've been build web API with Rails.
For this case,
I need to have define some resources for API.
So it's hard to figure out where to put something to narrows query results.
We could do specify things in AR scope;
class Post < ActiveRecord::Base
scope :published, -> { where(published: true) }
end
or
with class method;
class Post < ActiveRecord::Base
def self.published
where(published: true)
end
end
and serverside resources (I use jsonapi-resource);
class ContactResource < JSONAPI::Resource
attributes :name_first, :name_last, :full_name
def full_name
"#{#model.name_first}, #{#model.name_last}"
end
def self.updatable_fields(context)
super - [:full_name]
end
def self.creatable_fields(context)
super - [:full_name]
end
end
What makes you decide to where to put those type of query narrowing interface.
And what's the difference among those. (Especially class method vs scope is very confusing.)
Any idea?

I suggest you read the scope documentation:
Unlike Shirt.find(...), however, the object returned by Shirt.red is
not an Array but an ActiveRecord::Relation, which is composable with
other scopes; it resembles the association object constructed by a
has_many declaration. For instance, you can invoke Shirt.red.first,
Shirt.red.count, Shirt.red.where(size: 'small'). Also, just as with
the association objects, named scopes act like an Array, implementing
Enumerable; Shirt.red.each(&block), Shirt.red.first, and
Shirt.red.inject(memo, &block) all behave as if Shirt.red really was
an array.
Basically, using a scope is more flexible. Personally, I'd start by using a scope, and fall back to using a class method if using scope causes an edge case problem.

Related

implement each method in user class which has extend ActiveRecord::Base

Redefine each methods in ActiveRecord::Base for spec class User
This is what i know
class Rainbow
include Enumerable
def each
yield "red"
yield "orange"
yield "yellow"
yield "green"
yield "blue"
yield "indigo"
yield "violet"
end
end
r = Rainbow.new
r.select { |a| a.start_with?('r')} #=> ["red"]
Ok !!
Like this way what i want in User Model
class User < ActiveRecord::Base
include Enumerable
def user_ids
User.all.map(&:id) ## instead of this i want to write like User.map(&:id)
end
end
Actually There is lots of data in News model and in need only id from all the record To write the query like User.all.map(&:id) it taking lots of time.
1: For that i need to redefine each method but how ? but what line of codes i need to write in each method .
2: so that all the enumerable method can invoke on that classe`s object
Is there any other way.
Any help would be greatly appreciate.
This is not a good idea. The reason is because ActiveRecord classes (and therefore inner instance methods) can be both accessed as first-class object (when you call Model.foo) or via the ActiveRecord::Relation object and association proxy.
There is a very high chance that you will cause some hard-to-detect conflict at some point.
There is no real benefit of trying to do what you want to do. In fact, the method
class User < ActiveRecord::Base
include Enumerable
def user_ids
User.all.map(&:id)
end
end
can already be rewritten to
def user_ids
User.ids
end
that is a shorter version for
def user_ids
User.pluck(:id)
end
Note that both pluck and ids selects only the required field, hence they are way more efficient (both at Ruby level and at database level) than loading all the records and mapping a field.
Without mentioning that your code is probably wrong. In fact, you are defining an instance method that should be called
User.new.user_ids
whereas you probably expect to use it as
User.user_ids
hence you can define it as
class User < ActiveRecord::Base
def self.user_ids
# User it's implicit, its the current scope
ids
end
end
You can define each
class User < ActiveRecord::Base
include Enumerable
def each(&block)
# all returns a lazy-evaluated scope
# that responds to each
# Note that .each will trigger a query.
# In this case, that's effectively equivalent to to_a.each
all.each(&block)
end
end
but it will not bring you any advantage. Moreover, that will always trigger a query at the time you call the method, skipping the very handy lazy-load feature of active record.
In fact, ActiveRecord::Relation exists also as a performance improvement to take advantage of lazy-load.
Bottom line, if your goal is to not type User.all.map(&:id) then use a custom method, or use the Rails API effectively.
User.all.map(&:id)
can be written as
User.pluck(:id)
which is equivalent to
User.ids
that wrapped in a method becomes
class User
def self.user_ids
ids
end
end
User.user_ids

Having trouble chaining ActiveRecord::Associations record

I'm trying to retrieve an associated column named "contribution_amount" for each user but I'm getting undefined method error and I can't figure out why.
Controller has:
#payments = Payment.where(:contribution_date => Date.today).pluck(:user_id)
#users = User.where(:id => #payments).find_each do |user|
user.payments.contribution_amount
end
models have:
class User < ActiveRecord::Base
has_many :payments
end
class Payment < ActiveRecord::Base
belongs_to :user
end
Exact error in console is
`undefined method `contribution_amount' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Payment:0x007fb89b6b2c08>`
user.payments is a scope; that is, it represents a collection of Payment records. The contribution_amount method is only available on individual Payment records. You could say user.payments.first.contribution_amount, but I'm not sure that's your goal.
Are you trying to sum the contribution amounts? In that case, you'd want to use a method which aggregates collections of records: user.payments.sum(:contribution_amount).
Veering off-topic for a moment, it is generally better to push scoping methods down into your models. For example:
class User < ActiveRecord::Base
def self.with_payment_contribution_after(date)
joins(:payments).merge(Payment.with_contribution_after(date))
end
def self.with_contribution_amount
joins(:payments).group("users.id")
.select("users.*, sum(payments.contribution_amount) as contribution_amount")
end
end
class Payment < ActiveRecord::Base
def self.with_contribution_after(date)
where(:contribution_date => date)
end
end
# In your controller
#users = User.with_payment_contribution_after(Date.today)
.with_contribution_amount
# In a view somewhere
#users.first.contribution_amount
The advantages to structuring your code this way are:
Your scopes are not longer locked away in a controller method, so you can easily reuse them other places.
Your controller method can become simpler and more declarative. That is, it can express what information it wants, not how that information is acquired.
Breaking scopes down into smaller pieces implies that our code is better decomposed, and that which has been decomposed can be recomposed.
It's easier to test scopes via model unit tests then via controller testing.

How to make default_scope in Rails model dynamic?

In one of my Rails models I have this:
class Project < ActiveRecord::Base
belongs_to :user
default_scope order("number ASC")
end
Now the problem is that I want each user to be able to set his or her default_scope individually. For example, a user A might want default_scope order("date ASC"), another one might want default_scope order("number DESC").
In my User table I even have columns to store these values: order_column and order_direction.
But how can I make the default_scope in the model dynamic?
Thanks for any help.
As #screenmutt said, default scopes are not meant to be data-driven, they are meant to be model driven. Since this scope is going to change according to each user's data I'd use a regular scope for this.
#fmendez answer is pretty good but it uses default scope which I just explained why it is not recommended using this method.
This is what I'd do in your case:
class Post < ActiveRecord::Base
scope :user_order, lambda { order("#{current_user.order_column} #{current_user.order_direction}")}
end
Also a very important thing to notice here is SQL injection: Since you are embedding current_user.order_column and current_user.order_direction inside your query, you MUST ensure that the user can only feed these columns into the database with valid data. Otherwise, users will be able to craft unwanted SQL queries.
You won't want to use default_scope. What you do what is regular scope.
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
Scope | Ruby on Rails
You could do something like this:
def self.default_scope
order("#{current_user.order_column} #{current_user.order_direction}")
end
This should dynamically pick the values stored in the current_user's order_column and order_direction columns.
You can define a class method with whatever logic you require and set your default scope to that. A class method is identical to a named scope when it returns a relation,eg by returning the result of a method like order.
For example:
def self.user_ordering
# user ording logic here
end
default_scope :user_ordering
You may want to add a current_user and current_user= class methods to your User model which maintains the request user in a thread local variable. You would typically set the current user on your User model from your application controller. This makes current_user available to all your models for logic such as your sorting order and does it in a thread safe manner.

Scope for multiple models

I have several objects that all have an approved field.
What would be the best way to implement a scope to use across all models?
For example, I have a sighting object and a comment object. They both have to be approved by an admin before being availalbe to the public.
So how can I create a scope that returns comment.approved as well as sighting.approved respectively without repeating it on each model? Is this where concerns comes into play?
While just declaring a scope in each model is fine if you just want the scoping functionality. Using an ActiveSupport::Concern will give you the ability to add additional methods as well if that's something you think is going to happen. Here's an example:
# /app/models/concerns/approved.rb
module Approved
extend ActiveSupport::Concern
included do
default_scope { where(approved: false) }
scope :approved, -> { where(approved: true) }
end
def unapprove
update_attribute :approved, false
end
end
class Sighting < ActiveRecord::Base
include Approved
end
class Comment < ActiveRecord::Base
include Approved
end
Then you can make calls like Sighting.approved and Comment.approved to get the appropriate list of approved records. You also get the unapprove method and can do something like Comment.approved.first.unapprove.
In this example, I've also included default_scope which will mean that calls like Sighting.all or Comment.all will return only unapproved items. I included this just as an example, it may not be applicable to your implementation.
Although I've noticed the scope pulled from the concerns needs to be the last scope when concatenating scopes. I'm not quite sure why.
Comment.existing.approved
When I tried it as:
Comment.approved.existing
It failed silently.
And I take that back. I was migrating older code and using scopes with conditions instead of lambdas. When I replaced :conditions the scope order no longer mattered.
scope :existing, -> { where("real = 1") }
replaced
scope :existing, :conditions => "real = 1"

Why MyModel.all works in Rails?

i don't understand this little thing:
Suppose, we have "Condition" model
class Condition < ActiveRecord::Base
end
Why Condition.all works ?
Condition.all.each { |p| do_something }
This syntax tells us, that we have "Condition" class-object instanciated somewhere ?
Or is it some convention over configuration case ?
I asking this, because i want to override Condition.all method to return Conditions, sorted by "created_at" field value ?
I don't need to use sort method in place, i want to insert Conditions to, because in the entire project i need only one sorting
Thanks
Person.all is just an alias for Person.find(:all) (see the documentation here).
all, like find, is a class method on ActiveRecord::Base so doesn't require an instance in order to be called.
Update
To override a class method you need to remember the self. prefix. e.g. you can override all like this:
class Condition < ActiveRecord::Base
def self.all(*args)
# overridden implementation here
end
end
If you aren't clear on instance methods vs. class methods read this blog post which is a good summary,
However, if you just want to specify a default ordering you don't need to do this. You can just use default_scope:
class Condition < ActiveRecord::Base
default_scope :order => 'created_at'
end

Resources