Assign collection to query - ruby-on-rails

I have a module which returns timesheet records. When there is a scope parameter provided the items method scopes the timesheets so it only returns certain timesheets. How can I use the result of that query in another query? It is about using team_users in the super.where(user_id: team_users) query.
module Collections
class TimesheetCollection < Collection
module TeamScope
def items
if params[:scope].present?
team_users = User.from_team(#manager)
super.where(user_id: team_users)
else
super
end
end
end
attr_reader :ability, :params
def initialize(ability, params, manager)
#ability = ability
#params = params
#manager = manager
extend TeamScope
end
def items
Timesheet.unscoped
end
def paginated
extend Pagination
self
end
end
end

for active record you can pass in an array of ids/values. For your query you could try to do something like this:
team_users = User.from_team(#manager)
super.where(user_id: team_users.collect{|u| u.id})

Related

Chaining ActiveRecord_Relation in PORO

In a Rails 5.1 app, I have a query object (PORO) named CoolProducts.
class CoolProducts
def self.call(relation = Product.all)
...
# return an instance of Product::ActiveRecord_Relation
end
end
Now I need to limit the found Products based on the fact the name matches a string.
The following works
CoolProducts.call.where("name ILIKE ?", "%#{string}%")
However, I'd like to encapsulate the matching login within the CoolProducts class allowing to do something like
CoolProducts.call.including_in_name(string)
But I'm not sure where to start from.
Any ideas?
It will be difficult if you want any of your methods to be chainable or return ActiveRecord::Relation.
If you consider explicitly fetching the records when you're done chaining being ok, this should work:
class CoolProducts
def initialize(relation)
#relation = relation
end
def self.call(relation = Product.all)
new(relation).apply_scopes
end
attr_reader :relation
alias_method :fetch, :relation
def including_in_name(string)
tap { #relation = relation.where("name ILIKE ?", string) }
end
def apply_scopes
tap { #relation = relation.where(price: 123) }
end
end
Usage:
CoolProducts.call.including_in_name(string).fetch

Ruby add variables to an existing object?

How can I add variables to an existing obejct?
I have a list of chat rooms and I want to add a new variable for each chat to use at my view:
Example I want to add total users of chat
def index
chats_all = ChatRoom.all
#chats = Array.new
chats_all.each |chat|
chat.total_users = 10
#chats << chat
end
#chats
end
total_users is not an attribute of ChatRoom class.
[EDIT - explaim better after #jvillian great awnser]
I don't want total_users as an attribute of User class.
I just want to add as a variable to use at this one single page. For json rails already let my add new attributes to objects. Just need to use as_json().map and a merge()
Example:
def index
chats = chats.as_json().map {
|chat|
chat.merge(
total_users: 10
}
response = { chats: chats }
render json: response
end
Now I got an json with chats and each chat has total_users attribute.
I want to know if I can do something like this with objects, just add a temporary variable to use at index page.
Try
class ChatRoom < ActiveRecord::Base
attr_accessor :total_users
end
You can read more in the docs.
Then, index could look like:
def index
#chats = ChatRoom.all.map do |chat|
chat.total_users = 10
chat
end
end
Alternatively, I would be tempted to do something like:
class ChatRoom < ActiveRecord::Base
TOTAL_USERS = 10
attr_accessor :total_users
def total_users
#total_users || TOTAL_USERS
end
end
And then:
def index
#chats = ChatRoom.all
end
Now, you'll get
#chats.first.total_users
=> 10
You can set total_users to something else if you like, but it will default to 10.
Here's a potential approach using OpenStruct:
def index
#chats = ChatRoom.all.map do |chat|
OpenStruct.new(
chat.
attributes.
merge!(total_users: 10)
)
end
end
Now, you can do:
#chats.each do |chat|
puts chat.total_users
end
which will return 10.
BTW and TBH, I do something like that last sort of thing (using OpenStruct or custom decorators) all the time. In my more recent apps, views never have direct access to models.
Maybe you want to send the response to the view as an array and scan to show informations?
def index
#chats = ChatRoom.all.as_json().map { |chat| chat.merge("total_users" => 10) }
end
Then access #chats, which is actually an array of hashes, view:
<% #chats.each do |chat| %>
<p><%= chat["total_users"] %></p>
<% end %>
You can check how #chats is structured by <p><%= #chats %></p>
I maybe made some syntax error.
To create temporary custom Objects without add new attributes to database Struct solve my problem.
I can create a Struct with chat room info and total users
chat_info = Struct.new(:name, :total_users, :messages)
chat_temp = []
chats = ChatRoom.where(condition)
chats.each do |chat|
chat_temp << chat_info.new("nome", 100, messages)
end

How to combine two activerecord queries?

Lets say we have Event model, and SearchEngine class which performs searches on events.
SearchEngine has search method.
class SearchEngine
def search(event_scope)
return Event.all if event_scope.nil?
//event_scope combined with Event.where('name = ?','test')
end
end
I'd like to be able to pass event_scope like this:
SearchEngine.new.search(Event.where('start_time > ?',Time.now))
And the result would be the same as:
Event.where('start_time > ?',Time.now).where('name = ?','test')
How can I do this?
Scopes provide a merge method to merge with another scope, so this should work:
class SearchEngine
def search(event_scope)
return Event.all if event_scope.nil?
Event.where('name = ?','test').merge(event_scope)
end
end

Rails - Pass collection to ActiveModel object

I am using rails to make a datatable that paginates with Ajax, and I am following railscast #340 to do so.
This episode makes use of a normal ActiveModel Class called ProductsDatatable or in my case OrdersDatatable to create and configure the table. My question has to do with ruby syntax in this class. I am trying to pass a collection of orders to the OrdersDatatable object, from the controller. I want to access this collection in the fetch_orders method.
I create the table object like this in order.rb:
#datatable = OrdersDatatable.new(view_context)
#datatable.shop_id = #current_shop.id
#datatable.orders_list = #orders # which is Order.in_process
And my OrdersDatatable class looks like this: (the important parts which probably need to change is the second line in initialize and the first line in fetch_orders)
class OrdersDatatable
include Rails.application.routes.url_helpers
include ActionView::Helpers::DateHelper
include ActionView::Helpers::TagHelper
delegate :params, :h, :link_to, :number_to_currency, to: :#view
attr_accessor :shop_id, :orders_list
def initialize(view)
#view = view
#orders_list = self.orders_list
end
def current_shop
Shop.find(shop_id)
end
def as_json(options = {})
{
sEcho: params[:sEcho].to_i,
iTotalRecords: orders.count,
iTotalDisplayRecords: orders.count,
aaData: data
}
end
private
def data
orders.map do |order|
[
order.id,
order.name,
h(time_tag(order.date_placed.in_time_zone)),
order.state,
order.source,
order.payment_status,
h(order.delivered? ? 'shipped' : 'unshipped'),
h(number_to_currency order.final_total, unit: order.currency.symbol),
h(link_to 'details', edit_admin_shop_order_path(current_shop, order)),
h(link_to 'delete', admin_shop_order_path(current_shop, order), method: :delete, data: { confirm: 'Are you sure?' } ),
]
end
end
def orders
#orders ||= fetch_orders
end
def fetch_orders
orders = orders_list.order("#{sort_column} #{sort_direction}")
orders = orders.page(page).per_page(per_page)
if params[:sSearch].present?
orders = orders.where("title like :search", search: "%#{params[:sSearch]}%")
end
orders
end
def page
params[:iDisplayStart].to_i/per_page + 1
end
def per_page
params[:iDisplayLength].to_i > 0 ? params[:iDisplayLength].to_i : 10
end
def sort_column
columns = %w[id name date_placed state source payment_status delivered final_total]
columns[params[:iSortCol_0].to_i]
end
def sort_direction
params[:sSortDir_0] == "desc" ? "desc" : "asc"
end
end
When I change the first line in fetch_orders to this
orders = Order.in_process.order("#{sort_column} #{sort_direction}")
which is the hard-coded equivalent, it does work. So I just need the correct syntax
Short answer: If you've got an array, and want to sort it, use the sort_by method:
orders = orders_list.sort_by{|order| "#{order.sort_column} #{order.sort_direction}"}
Long answer: The reason your original code doesn't work is that in this case
Order.in_process.order("#{sort_column} #{sort_direction}")
you are building a query. in_process is a named scope (passing in some conditions), and .order tells rails what to order the query by. Then, when it runs out of chained methods, the query executes (runs some sql) and gets the records out of the DB to build a collection of objects.
Once you are working with a collection of objects, you can't call the .order method on it, as that's just used to assemble an sql query. You need to use Array#sort_by instead. sort_by takes a code block, into which is passed each object in the collection (as order in my example but you could call it anything, it's just a variable name).
BTW, if you just want to call a method on all the objects to sort them, you can use a "shortcut syntax" like .sort_by(&:methodname). This uses a little trick of ruby called Symbol#to_proc (http://railscasts.com/episodes/6-shortcut-blocks-with-symbol-to-proc).
So, for example, if there was a method in Order like so
def sort_string
"#{self.sort_column} #{self.sort_direction}"
end
then you could change your code to
orders = orders_list.sort_by(&:sort_string)
which is neat.
If you have an array, then you can sort like this.
orders = orders_list.sort! {|a,b| a.sort_column <=> b.sort_direction}

How do I pass a var from one model's method to another?

Here is my one model..
CardSignup.rb
def credit_status_on_create
Organization.find(self.organization_id).update_credits
end
And here's my other model. As you can see what I wrote here is an incorrect way to pass the var
def update_credits
#organization = Organization.find(params[:id])
credit_count = #organization.card_signups.select { |c| c.credit_status == true}.count
end
If it can't be done by (params[:id]), what can it be done by?
Thanks!
Ideally the data accessible to the controller should be passed as parameter to model methods. So I advise you to see if it is possible to rewrite your code. But here are two possible solutions to your problem. I prefer the later approach as it is generic.
Approach 1: Declare a virtual attribute
class CardSignup
attr_accessor call_context
def call_context
#call_context || {}
end
end
In your controller code:
def create
cs = CardSignup.new(...)
cs.call_context = params
if cs.save
# success
else
# error
end
end
In your CardSignup model:
def credit_status_on_create
Organization.find(self.organization_id).update_credits(call_context)
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find(call_context[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Approach 2: Declare a thread local variable accessible to all models
Your controller code:
def create
Thread.local[:call_context] = params
cs = CardSignup.new(...)
if cs.save
# success
else
# error
end
end
Update the Organization model. Note the change to your count logic.
def update_credits
#organization = Organization.find((Thread.local[:call_context] ||{})[:id])
credit_count = #organization.card_signups.count(:conditions =>
{:credit_status => true})
end
Use an attr_accessor.
E.g.,
class << self
#myvar = "something for all instances of model"
attr_accessor :myvar
end
#myothervar = "something for initialized instances"
attr_accessor :myothervar
then you can access them as ModelName.myvar and ModelName.new.myvar respectively.
You don't say whether you're using Rails 2 or 3 but let's assume Rails 2 for this purpose (Rails 3 provides the a new DSL for constructing queries).
You could consider creating a named scope for in your Organization model as follows:
named_scope :update_credits,
lambda { |id| { :include => :card_signup, :conditions => [ "id = ? AND card_signups.credit_status = TRUE", id ] } }
And then use it as follows:
def credit_status_on_create
Organization.update_credits(self.organization_id)
end
Admittedly I don't quite understand the role of the counter in your logic but I'm sure you could craft that back into this suggestion if you adopt it.

Resources