Rails partial need to be rendered twice with different content, but output is equal - ruby-on-rails

I have to output 2 blocks on the same page, each one of them must contain 3 random posts from database.
I have simple model
class Post < ActiveRecord::Base
scope :random, -> { order('RANDOM()') }
end
I put the ActiveRecord code in application_helper.rb:
module ApplicationHelper
def random_posts(num = 3)
posts = Post.uncached do
Post.random.limit(num)
end
end
end
then in layout I use this call twice (Slim template engine used)
= render random_posts
which uses this partial app/views/posts/_post.html.slim
a.btn = post.title
Blocks are filled with 3 random posts from database, but they are the same in each blocks! Why is it so? Each block has to contain different posts.
I've created a repo here with simple demonstration

I got this to work by flipping uncached to cache. I was experimenting on the Post model, but you could probably drop this in your helper just as easily:
class Post < ActiveRecord::Base
def self.random_posts(n = 3)
cache do
random.limit(n)
end
end
end
For two calls of the method using uncached, the ActiveRecord log lines are Post Load ... and CACHE ..., but using cache, they are both Post Load.... I really wish I could explain why this works, but it's completely counterintuitive and makes no sense to me.

Forking your code, it seems that the collection Post.random is being cached in Rails in some way. If you add a debugger on the random_posts in ApplicationHelper:
Post.random.map(&:id)
Will have the same collection every time.
Taken from this blogpost, you could use this as an alternative:
In ApplicationHelper.rb:
def self.random_posts(num = 3)
ids = Post.pluck(:id).shuffle[0..4]
Post.where(id: ids)
end

Related

How do I run a calculation in my ruby on rails model before the view is rendered?

I have an application where users complete a number of tests. The results of these tests is then stored in the database.
class TestOne < ActiveRecord::Base
belongs_to :user
before_save :calculate_total
private
def calculate_total
self.total = self.question_1 + self.question_2
end
end
I then have a summary page which performs some calculations (in the summary page model) and then these values are displayed on the summary page.
class Summary < ActiveRecord::Base
def self.amount
amount = 0
#users.each do |u|
if u.test_ones.present?
amount += 1
else
end
if u.test_twos.present?
else
end
return amount
end
end
The problem I am having is that the calculations (amount) do not update when new data is saved. I am assuming there is some code I can put in the model to make it recalculate before the summary view is rendered (similar to the before_save in the other model)?
Thanks in advance x
You can use before_action or before_filter (deprecates in Rails 5.1) in your controller (the controller control's presenting the view layer and the model handles the logic of the records. Depending on how complex your action is, this may be a bad solution. If it takes a long time, or the flow gets complex, then it is better to keep this out of the controller. See this post for more info. Also note the render flow in this example:
def update
if #tree.update(tree_params) # Render / Re-route based on truthy.
redirect_to(#tree)
else
render('edit')
end
end
However, there are other libraries that specifically handle things like this. For example, ReactJs will automatically re-render components when the 'properties' or 'state' of the component changes. It may be too much overhead to implement React just for this, but if you anticipate having lots of dynamic page data it may be better to use some facet of javascript to automate view layer components.

Rails; Fetch records within initializer

I've been wondering it is common to fetch records within initializer?
Here this is an example for service object to fetch records and generated pdf receipt file.
Input is invoice uuid, and fetch the related records such as card detail, invoice items within initialier.
class Pdf::GenerateReceipt
include Service
attr_reader :invoice, :items, :card_detail
def initialize(invoice_uuid)
#invoice ||= find_invoice!(invoice_uuid) # caching
#items = invoice.invoice_items
#card_detail = card_detail
end
.....
def call
return ReceiptGenerator.new(
id: invoice.uuid, # required
outline: outline, # required
line_items: line_items, # required
customer_info: customer_info
)
rescue => e
false, e
end
.....
def card_detail
card_metadata = Account.find(user_detail[:id]).credit_cards.primary.last
card_detail = {}
card_detail[:number] = card_metadata.blurred_number
card_detail[:brand] = card_metadata.brand
card_detail
end
end
Pdf::GenerateReceipt.('28ed7bb1-4a3f-4180-89a3-51cb3e621491') # => then generate pdf
The problem is if the records not found, this generate an error.
I could rescue within the initializer, however that seems not common.
How could I work around this in more ruby way?
This is mostly opinion and anecdotal, but I prefer to deal with casting my values as far up the chain as possible. So i would find the invoice before this object and pass it in as an argument, same with the card_detail.
If you do that in this class, it will limit the responsibility to coordinating those two objects, which is way easier to test but also adds another layer that you have to reason about in the future.
So how i would handle, split this into 4 separate things
Invoice Finder thing
Card Finder thing
Pdf Generator that takes invoice and card as arguments
Finally, something to orchestrate the 3 actions above
Hope this helps.
Addition: Check out the book confident ruby by avdi grimm. It's really great for outlining handling this type of scenario.

Instance variable available in ActiveAdmin index

I have an ActiveAdmin resource whose collection requires some particularly complex queries to avoid a bunch of n+1's. I tried to modify controller#scoped_collection with all the proper .includes or .joins, but it was still doing n+1's.
I've since changed to an approach of just doing the queries by hand and dumping the relevant results into Hashes. Then in my index view, I grab the data from the hashes, rather than from the scoped_collection.
Unfortunately, I can't figure out how to make an instance variable available in the index scope. I got around that by putting it into the params hash (which is available in the index scope), but that ends up breaking the filtering.
So, is there some way I'm not seeing to scope variables from inside scoped_collection or some other sort of before_filter-like method that will be available inside index?
Sample code:
ActiveAdmin.register ComplexResource do
actions :index
controller do
def scoped_collection
#complex_collection_relation = ComplexResource.where(crazy: :stuff)
# a bunch more lines of code to do avoid n+1's and put info into supporting data hash
#supporting_data_hash = {}
complex_collection_relation.each{|r| supporting_data_hash[r.id] = stuff }
# my hacky workaround because #supporting_data_hash isn't available below
params[:supporting_data_hash] = #supporting_data_hash
return #complex_collection_relation
end
end
filter :my_filter_that_gets_broken, as: :string
index do
column :name
column :complex_attribute_1 do |r|
params[:supporting_data_hash][r.id][:complex_attribute_1]
end
# how can I do something like this without using the params workaround
# column :complex_attribute_1 do |r|
# #supporting_data_hash[r.id][:complex_attribute_1]
# end
end
end
I know this is an old question but it's the first one that comes up when googling. Also, I'm not 100% sure it applies to the duplicate question so I'll leave my answer here:
Instance variables can be reached as helpers when defining columns for a resource. E.g.:
ActiveAdmin.register Company do
controller do
before_action :load_data, only: :index
def load_data
#loaded_data = expensive_operation
end
end
index do
column 'Something' do |company|
loaded_data[company.id]
end
end
end
I found a reference to it on this answer on Github issues
#Jiemurat 's answer to this question is the solution. This question is duplicative of How do I use instance variables, defined in the controller, in the view with ActiveAdmin?

How to display an specific item of database

My rails app has a database set.
def index
#clubs = Club.all
end
This is my controller.
If i type in my Index.html.erb
<% #clubs.each do |club| %>
<%= club.name %>
<% end %>
I get all the names of my database show in my index view.
What if I just want to pick one or just a couple?
Thru the rails console i can by typing c=Club.find(1) 1 by default takes id=1.
So how can i just display several ID's and not all one the database in the same index.html.erb.
thanks anyway!
Try this:
Let us consider that params[:ids] contains all the ids that belong to the records you want to get.
def index
#clubs = Club.where(id: params[:ids])
end
Fix
The straightforward answer here is to recommend you look at the ActiveRecord methods you can call in your controller; specifically .where:
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def index
#clubs = Club.where column: "value"
end
end
This will populate the #clubs instance variable with only the records which match that particular condition. Remember, it's your Rails app, so you can do what you want with it.
Of course, it's recommended you stick with convention, but there's nothing stopping you populating specific data into your #clubs variable
--
RESTful
As someone mentioned, you shouldn't be including "filtered" records in an index action. Although I don't agree with this idea personally, the fact remains that Rails is designed to favour convention over configuration - meaning you should really leave the index action as showing all the records
You may wish to create a collection-specific action:
#config/routes.rb
resources :clubs do
collection do
get :best #-> domain.com/clubs/best
end
end
#app/controllers/clubs_controller.rb
Class ClubsController < ApplicationController
def best
#clubs = Club.where attribute: "value"
render "index"
end
end
There are several ways to select a specific record or group of records from the database. For example, you can get a single club with:
#club = Club.find(x)
where x is the id of the club. Then in your view (the .html.erb file), you can simply access the #club object's attributes.
You can also cast a wider net:
#disco_clubs = Club.where(type: "disco") # returns an ActiveRecord Relation
#disco_clubs = Club.where(type: "disco").to_a # returns an array
And then you can iterate over them in the same manner you do in your index.html.erb. Rails has a rich interface for querying the database. Check it out here.
Also note that individual records - such as those selected with the find method - are more commonly used with the show action, which is for displaying a single record. Of course, that's for generic CRUD applications. It't not a hard rule.
change
def index
#clubs = Club.all
end
to this
def index
#clubs = Club.find(insert_a_number_that_is_the_id_of_the_club_you_want)
end
Querying your database is a complex thing and gives you a ton of options so that you can get EXACTLY what you want and put it into your #clubs variable. I suggest reading this part of the rails guide
It should also be noted that if you're only going to query your database for one record then change #clubs to #club so you know what to expect.

Organizing site navigation actions in Rails

I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end

Resources