Kaminari combine two collections and place heading in between - ruby

I have a page with two collections of items.
def index
#collection1 = Model.collection1
#collection2 = Model.collection2
end
I'm aware of how to combine them together to paginate with Kaminari, however I need to place a <h1>collection 2</h1> header before the items of #collection2 starts (if any).
The page would be something like this:
..Item from collection 1..
..Item from collection 1..
..Item from collection 1..
<h1>collection 2</h1>
..Item from collection 2..
..Item from collection 2..
Im having a hard time figuring out a way to do this. Any idea?

I have come up with the following solution.
def index
#collection1 = Model.collection1
#collection2 = Model.collection2
#collection1_paginated = #collection1.page(params[:page])
#collection2_paginated = Kaminari.paginate_array(#collection1 + #collection2).page(params[:page])
.padding(#collection1_paginated.size)
.take(#collection1_paginated.limit_value - #collection1_paginated.size)
#collection1_or_collection2 = Kaminari.paginate_array(#collection1 + #collection2).page(params[:page])
end
And in the views you can do:
= render partial: 'model', collection: #collection1_paginated
- display_collection_2_heading?
h1 collection 2
= render partial: 'model', collection: #collection2_paginated
= paginate #collection1_or_collection2
To display the Collection 2 heading create the following helper.
def display_collection_2_heading?
#collection1.present? && ((#collection1.count / #collection1_paginated.limit_value)+1) == #collection1_paginated.current_page
end
This helpers makes sure the heading only goes one only time after the last item from #collection1.

Related

Reduce SQL Queries from Includes assoc

I try to reduce SQL queries from my Rails application.
I have some controller like:
class Rest::MyController < Rest::BaseController
def show
render xml: some_model, status: :ok
end
private
def my_associations
[
:model2,
:model3,
:model4,
]
end
def some_model
#some_model ||= SomeModel.includes(my_associations).where(id: test_params[:id])
end
def test_params
params.permit(:id)
end
end
To avoid N + 1 I use includes, so basically when i try to execute some_model method, AR make lot of call's like that (SELECT ALL FROM):
SomeModel Load (1.7ms) SELECT `model2`.* FROM `model2` WHERE `model2`.`type` IN ('SomeModel') AND `model2`.`is_something` = 0 AND `model2`.`id` = 1
SomeModel Load (1.7ms) SELECT `model3`.* FROM `model3` WHERE `model3`.`type` IN ('SomeModel') AND `model3`.`is_something` = 0 AND `model3`.`id` = 1
SomeModel Load (1.7ms) SELECT `model4`.* FROM `model4` WHERE `model4`.`type` IN ('SomeModel') AND `model4`.`is_something` = 0 AND `model4`.`id` = 1
This is only example
Now, through my serializer I would like to get only selected columns for model2, model3 and model4
Unfortunately Active record make a call like SELECT model2.* FROM
For example, for model2 serializer i try to get only (:id, :name) columns.
Is it possible to make a call like ?
SELECT some_model.*, model2.id, model2.name FROM `some_model`
instead
SELECT `model2`.* FROM `model2` WHERE `model2`.`type` IN ('SomeModel')
If you want to use Rails's includes feature, then no, there isn't an especially good way to selectively control the columns from included models.
If you're looking for help to optimize the query, you'll need to provide more specifics about the data schema and the desired output.

How to use scopes to filter data by partial variable

I have some working script filtering my results with Active Record Scoping. Everything works fine when i want to filter by comparing params with data from database.
But i have some function counting car price inside _car.html.erb partial, the result of this function depends on params result.
How can i scope search results by result of this function and show only cars which are under some price (defined in params).
Some code to make it more clear:
car.rb (model file)
scope :price_leasing, -> (price_leasing) { where('price_leasing <= ?', price_leasing) }
# for now price_leasing is getting price from database
scope :brand, -> (brand) { where brand: brand }
scope :car_model, -> (car_model) { where car_model: car_model }
scope :category, -> (category) { where category: category }
cars_controller.rb (controller file)
def index
#cars = Car.where(nil)
#cars = #cars.price_leasing(params[:price_leasing]) if params[:price_leasing].present?
#cars = #cars.brand(params[:brand]) if params[:brand].present?
#cars = #cars.car_model(params[:car_model]) if params[:car_model].present?
#cars = #cars.category(params[:category]) if params[:category].present?
#brands = Brand.all # importing all car brands into filters
end
in index.html.erb i have "render #cars" code
<%=
if #cars.size > 0
render #cars.where(:offer_status => 1)
else
render html: '<p>Nie znaleziono pasujących wyników.</p>'.html_safe
end
%>
inside _car.html.erb file i have function from helper
<h3 class="car-cell__price"><%= calculate_car_price(car.price, car.id) %> <span class="car-cell__light-text">zł/mc</span></h3>
my calculate_car_price() function inside helper
def calculate_car_price(car_price, car_id)
car = Car.find(car_id)
fullprice = car_price
if params[:price_leasing].present?
owncontribution = params[:price_leasing].to_i
else
owncontribution = car.owncontribution
end
pv = fullprice - owncontribution + (0.02 * fullprice)
if params[:period].present?
carperiod = params[:period].to_i
carprice = (Exonio.pmt(0.0522/60, carperiod, pv)) * -1
else
carprice = (Exonio.pmt(0.0522/60, 60, pv)) * -1
end
p number_with_precision(carprice, precision: 0)
end
i would love to scope by the result of this function. Is it possible?
The thing about scopes are that they are implemented at the database level. You want to select records that have a value
To do what you want at the DB level, you would need to a virtual column in the database, the implementation will change based on which database product you're using (I would not expect a virtual column definition in postgreSQL to be the same as a virtual column definition in mySQL)
So I'm not sure there's an optimal way to do this.
I would suggest you build your own class method and instance method in your model Car. It would be less performant but easier to understand and implement.
def self.car_price_under(target_price, params)
select { |v| v.car_price_under?(target_price, params) }
end
def car_price_under?(target_price, params)
full_price = price
if params[:price_leasing].present?
my_own_contribution = params[:price_leasing].to_i
else
my_own_contribution = owncontribution
end
pv = full_price - my_own_contribution + (0.02 * full_price)
if params[:period].present?
car_period = params[:period].to_i
new_car_price = (Exonio.pmt(0.0522/60, car_period, pv)) * -1
else
new_car_price = (Exonio.pmt(0.0522/60, 60, pv)) * -1
end
new_car_price <= target_price
end
This would let you do...
#cars = Car.where(nil)
#cars = #cars.brand(params[:brand]) if params[:brand].present?
#cars = #cars.car_model(params[:car_model]) if params[:car_model].present?
#cars = #cars.category(params[:category]) if params[:category].present?
#cars = #cars.car_price_under(target_price, params) if target_price.present?
Note that this is happening in rails, NOT in the database, so the car_price_under method should be called after all other scopes to minimise the number of records that need to be examined. You cannot chain additional scopes as #cars would be an array... if you want to be able to chain additional scopes (or you want an active record relation, not an array) you could do something like #cars = Car.where(id: #cars.pluck(:id))

How should I change his script so that it works for collections and not just one product in Shopify?

The script is a template that comes with the script editor app in Shopify. I need to make it work so that if you buy one product from a collection, you get another one free from that collection. This script works only for buying the same product. Here is the script:
PAID_ITEM_COUNT = 2
DISCOUNTED_ITEM_COUNT = 1
# Returns the integer amount of items that must be discounted next
# given the amount of items seen
#
def discounted_items_to_find(total_items_seen, discounted_items_seen)
Integer(total_items_seen / (PAID_ITEM_COUNT + DISCOUNTED_ITEM_COUNT) * DISCOUNTED_ITEM_COUNT) - discounted_items_seen
end
# Partitions the items and returns the items that are to be discounted.
#
# Arguments
# ---------
#
# * cart
# The cart to which split items will be added (typically Input.cart).
#
# * line_items
# The selected items that are applicable for the campaign.
#
def partition(cart, line_items)
# Sort the items by price from high to low
sorted_items = line_items.sort_by{|line_item| line_item.variant.price}.reverse
# Create an array of items to return
discounted_items = []
# Keep counters of items seen and discounted, to avoid having to recalculate on each iteration
total_items_seen = 0
discounted_items_seen = 0
# Loop over all the items and find those to be discounted
sorted_items.each do |line_item|
total_items_seen += line_item.quantity
# After incrementing total_items_seen, see if any items must be discounted
count = discounted_items_to_find(total_items_seen, discounted_items_seen)
# If there are none, skip to the next item
next if count <= 0
if count >= line_item.quantity
# If the full item quantity must be discounted, add it to the items to return
# and increment the count of discounted items
discounted_items.push(line_item)
discounted_items_seen += line_item.quantity
else
# If only part of the item must be discounted, split the item
discounted_item = line_item.split(take: count)
# Insert the newly-created item in the cart, right after the original item
position = cart.line_items.find_index(line_item)
cart.line_items.insert(position + 1, discounted_item)
# Add it to the list of items to return
discounted_items.push(discounted_item)
discounted_items_seen += discounted_item.quantity
end
end
# Return the items to be discounted
discounted_items
end
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && product.id == 11380899340
end
discounted_line_items = partition(Input.cart, eligible_items)
discounted_line_items.each do |line_item|
line_item.change_line_price(Money.zero, message: "Buy one Bolur, get one Bolur free")
end
Output.cart = Input.cart
I tried changing, what seems to be the relevant code:
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && product.id == 11380899340
end
to this:
eligible_items = Input.cart.line_items.select do |line_item|
product = line_item.variant.product
!product.gift_card? && **collection.id** == 123
end
but I get an error:
undefined method 'collection' for main (Your Cart)
undefined method'collection' for main (No Customer)
Two things here:
line_item.variant.product does not have the property collections. For that, you want to use line_item.product (docs) – which (should...see point two) expose all of the methods and properties of the product object.
However, in my attempt to do something similar to you (discount based on product) I tried iterating over line_item.variant – and am always hitting the error of undefined method 'product' for #<LineItem:0x7f9c97f9fff0>. Which I interpret as "line_items accessed in cart scripts can only be at the variant level".
So, I wonder if this is because the cart only contains variants (product/color/size) – so we aren't actually able to access the line_items by product, and only by variant.
I tired iterating over line_item.product_id, which also throws a similar error. I think we just have to try to do some hacky thing at the variant level.
I am going to see if I can access the product by the variant ID...back to the docs!
You actually can't do a collection, so you'd need to modify the script to work with a product type or tags. That script will need to be heavily modified to work for a number of products and not multiples of the same

Filtering models with pagination only filters first page - rails 3

I have a model which I am filtering based on some parameters given by the user. The filtering works just fine but once
I start using pagination, only the records from the first page are filtered and the others are ignored.
This is my code in the controller for filtering:
#start_date = params[:start_date]
#registers = Register.all.order("payment_date DESC").page(params[:page]).per(params[:all] ? nil : Kaminari.config.default_per_page)
#registers.delete_if { |r| !(r.payment_date <= #end_date && r.payment_date >= #start_date) if (#start_date.present? && #end_date.present?) }
And in the view I use <%= paginate #registers %> to paginate the list.
Like #Raffael mentioned in a comment above, I had to use #registers = #registers.where(..) instead of #registers.delete_if(..) because it "screws your page numbers up".

SilverStripe 3: Loop greatgrandchildren pages WITH OUT grouping by parent

In my template I'm looping my child pages as 2 separate lists and then their grandchild pages under these. But I want these greatgrandchild pages to be sorted as an entire list and not divided / grouped by their parents.
Simply loop after loop does not work as all greatgrandchild pages should be put in order and not grouped by parent then sorted.
I know this could be done by a function to get all the greatgrandchildpages and sort them by a chosen method (e.g. alphabetically). But I don't want to get ALL of them as I need them just the grandchildren for each child.
PAGE - to show list
Child - List Heading
GrandChild - not shown in list
GreatGrandChild - Listed and ALL sorted Alphabetically
On Page you can get the IDs of all children (which are parents of the grand children) using getIDList():
$ids = Page::get()->filter(array('ParentID' => $this->ID))->getIDList();
Now you can get all Pages (or whatever pagetype you want) by ParentID:
$grandChildren = Page::get()->filter(array('ParentID' => $ids));
Same for grand-grandchildren....
$grandGrandChildren = Page::get()->filter(array('ParentID' => $grandChildren->getIDList()));
You can sort this DataList by whatever you want, e.g.
$sortedgrandGrandChildren = $grandGrandChildren->sort('Title', 'ASC');
edit:
$this->Children() does return an ArrayList, not a DataList, so we have to get all children manually. Be aware, this doesn't respect canView(), so we get all pages, if the current user has permissions or not. For a simple website this should be ok.
Using $this->Children()->column('ID') you get the filtered result.
A ready to use method could be:
public function getGrandGrandChildren() {
$ids = Page::get()->filter(array('ParentID' => $this->ID))->getIDList();
$grandChildren = Page::get()->filter(array('ParentID' => $ids));
$grandGrandChildren = Page::get()->filter(array('ParentID' => $grandChildren->getIDList()));
return $grandGrandChildren->sort('Title', 'ASC');
}
And in your template:
<% loop $GrandGrandChildren %>
$Title
//do whatever you want
<% end_loop %>
SiteTree::get()->filter('ParentID',SiteTree::get()->filter('ParentID',$this->Children()->column('ID')))->filterByCallback(function($page){return $page->canView()})

Resources