The following code it taken from a spree_recently_viewed gem
Controller
after_action :recently_viewed, only: :show
def recently_viewed
id = #product.id
rvp = (session['recently_viewed_products'] || '').split(', ')
rvp.delete(id)
rvp << id unless rvp.include?(id.to_s)
rvp_max_count = 5
rvp.delete_at(0) if rvp.size > rvp_max_count.to_i
session['recently_viewed_products'] = rvp.join(', ')
end
Helper
module ProductsHelper
def cached_recently_viewed_products_ids
(session['recently_viewed_products'] || '').split(', ')
end
def cached_recently_viewed_products
Product.find_by_array_of_ids(cached_recently_viewed_products_ids)
end
end
Model
class Product < ActiveRecord::Base
def self.find_by_array_of_ids(ids)
products = Product.where('id IN (?)', ids)
ids.map { |id| products.detect { |product| product.id == id.to_i } }.compact
end
end
looking at this line in the controller
rvp.delete_at(0) if rvp.size > rvp_max_count.to_i
it is only replacing the value at index 0. Is there a way I can add push pop style so that when a new record is added value at 0 for example moves to 1 and 1 to 2 and so on and the last one gets poped out. and if already exist moves to index 0.
Use array #unshift method to push into the beginning of an array, and #shift to pop from the beginning of the array.
PS: According the whole controllers and models code. I don't know why both too comlpex... models method ::find.. can be simplified to just Product.where('id IN (?)', ids) or to Product.where(id: ids) (thanx to #bbozo) in the newer version of Rails.
Related
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
I am iterating through a list of records. I need to check that if a record is first do XYZ and if not do ABC. Unfortunately I cant do this:
user = User.first
or
user = User.find(:id)
user.first?
Solution posted below
1. Make method to grab next and previous records
def next
[Model].where("id > ?", id).first
end
def prev
[Model].where("id < ?", id).last
end
2. Make method to check if record is first
def first?(record)
[Model].first == record
end
3. check if record is first
records.each do |record|
if record.first?(record)
record.update_attributes(attr: record.attr + record.attr)
else
prev_rec = [Model].find(record.id).prev
record.update_attributes(attr: prev_rec.attr + record.attr )
end
end
returns true or false
One improvement i would make sure that [Model].first is persistent so that it doesn't make a call to the database each time the loop is run.
I'm hacking away at a rails project and I wanted to modify the number of items that end up on a particular page. The page gets populated via an array of items.
For the life of me I can't figure out how to make it show only 2 instead of 4 items.
In the haml file there is this section:
%ul.story-list
- #stories.each do |story|
%li
- unless story.image.blank?
.img-container{ class: ((story.video.blank?)? "": "video-container") }
= image_tag(story.image_url, alt: story.name, class: ((story.video.blank?)? "": "js-has-video"), :video => story.video)
.story-data
%h4= story.name
%h5.location= story.location
%p.quote= story.story
- if story.get_connected?
= link_to 'Get Connected', connect_path
- elsif story.gather_supplies?
= link_to 'Gather Supplies', supplies_path
- elsif story.make_a_plan?
= link_to 'Make a plan', plan_path
The page shows up (on the server) with four story items, I want it to only show two. I was expecting to open the haml file and just delete some lines (or comment them out). I'm so confused.
So, I suspect the number of stories comes from a controller or something like that. ..but maybe it is coming from the placeholder data on the server?
In case you are inspired to help me, all the code is here
https://github.com/city72/city-72
The exact page I'm trying to modify is this one, I want it to only have two stories:
http://collier72.herokuapp.com/stories
Weirdly, in my local environment I can't edit the stories at all. That's what makes me thing the number of items comes from the data.
The stories controller is this tiny little file that doesn't specify the number of stories:
class StoriesController < ApplicationController
after_filter :static_content
def index
all_stories = EmergencyStory.order("index,id ASC").all
#selected_story = all_stories.select {|s| s.selected}.first
#stories = all_stories.collect.select {|s| !s.selected}
end
end
Open up this file:
https://github.com/city72/city-72/blob/master/app/controllers/stories_controller.rb#L8
Change that line from this:
#stories = all_stories.collect.select {|s| !s.selected}
to this:
#stories = all_stories.collect.select{|s| !s.selected}.slice(0,2)
From what I can tell, the fact it is returning 4 isn't intentional, it's just what is in the database. The slice(0,2) will return the first two items.
First, you have 3 stories that you are looking for, not 2. You have your #selected_story and then the remaining #stories. Second, you are retrieving ALL of the stories which will not scale when you get many stories in the database, so rendering this page will slow down over time. So you need to limit the number of records being returned by the database.
Get the selected story.
Then get the two next stories.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.where(selected: true).first
#stories = EmergencyStory.where(selected: false) # don't get selected
.limit(2) # limit records returned
.order("index,id ASC")
.all
end
end
If you were to further refine this you should put those two queries into methods into EmergencyStory.
class StoriesController < ApplicationController
after_filter :static_content
def index
#selected_story = EmergencyStory.selected_story
#stories = EmergencyStory.recent_stories
end
end
class EmergencyStory < ActiveRecord::Base
def self.selected_story
where(selected: true).first
end
def self.recent_stories
where(selected: false).limit(2).order('index,id ASC').all
end
end
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}
I have this class method:
def self.default_column
"created_at"
end
How can I rewrite the following function, so that I can make use of my default_column method?
def next
User.where("created_at > ?", created_at).order('created_at ASC').first
end
I tried things like these...
def next
User.where("#{default_column} > ?", default_column).order('#{default_column} ASC').first
end
... but I must be awfully wrong here because it doesn't work at all.
Thanks for any help.
You can use:
def next
User.where("#{User.default_column} > ?", self.send(User.default_column)).order("#{User.default_column} ASC").first
end
Or even better
def next
klass = self.class # This is supposing you are inside User model
# Otherwise just use klass = User
klass.where("#{klass.default_column} > ?", self.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
Notice that if you handle the method in this way, you cannot chain it: like User.where(name: 'something').next
If you want to achieve this, you have to move next to be def self.next and in that case, you have to pass an instance of the user to it, like this:
def self.next(user)
klass = user.class
klass.where("#{klass.default_column} > ?", user.send(klass.default_column))
.order(klass.arel_table[klass.default_column].asc)
end
In this way you can write something like: User.where(name: 'test').next(#user). You can optionally chain .first to get directly the result, but in this way you will not be able to chain other things, like User.where(name: 'test').next(#user).where(email: 'my#mail.com')
Finally, if you want pure AREL (for portability)
def self.next(user)
klass = user.class
arel = klass.arel_table
column = klass.default_column # This helps cleaning up code
column_value = user.send(column)
klass.where(arel[column].gt(column_value))
.order(arel[column].asc)
end
def next
default_column = self.class.default_column
User
.where("#{default_column} > ?", send(default_column))
.order("#{default_column} ASC")
.first
end