Patterns for fetching data inside a loop in the view? - html

Here's an example of a 'component' that I have.
Is this example, the rails cache is hit once every loop. Is there a way to avoid this?
It is only used in my mobile views. Which means I fetching the data in the controller will fetch unnecessary data for the desktop views.
Is it possible to avoid doing show_ids = current_user.get_show_ids in the view, outside the loop?
View:
#shows.each do |show|
if logged_in?
if current_user.is_following(show) ### Hitting the cache (!)
#remove button
else
#add button
end
else
#something else
end
end
User model:
def is_following(show)
get_show_ids.include?(show.id)
end
def get_show_ids
Rails.cache.fetch([self, "all_shows_ids", "v3"]) do
Following.where("user_id = ?", id).pluck(:show_id)
end
end
I've tried doing #ids ||= get_show_ids.include?(show.id) with the same result.

I assume that you have the models Show and User connected by a Following model for the n:m relation.
When you retrieve the shows, you can left join the Followings with a user_id of current-user. The result set will have the fields for Following set if there is such a following, and not if the user is not following.
Note you need to select the fields from Following model explicitly, otherwise the join will only return fields from the Show model

Related

Use a custom query when generating CSV in Activeadmin

I have additional calculation columns (based on joins) I want to include in my CSV.
If I open and calculate it individually for every record
csv do
column :complicated_calculation {  |r| r.calculate_things }
end
it's going to take a long time to generate with thousands of records.
I need to customize the SELECT query for when my CSV is generated and then use the columns in that query. How do I do that?
Customizing resource retrieval, in documentation, shows you how without rewriting the whole csv builder: modifying scoped_collection.
So if you have your query nicely waiting in your model:
class Person < ActiveRecord::Base
def self.with_calculation
select("people.*, (mumbo + jumbo * fumbo) "\
"AS complicated_calculation") # notice we name the attribute here
.joins("LEFT JOIN games ON person_id = people.id")
.group("people.id")
end
end
with your calculation you can do:
controller do
def scoped_collection
super.with_calculation
end
end
and then your CSV will have the attribute for free:
csv do
column :complicated_calculation
end

ActiveRecord: retrieve mySQL data based on one field in URL

I have a model called "blog", which has several columns, including one called "token".
I want to retrieve a row based on the token that's in a url - for instance, if the user goes to /blog/post1, I want to retrieve the row where token = post1
In my app setup I'm doing:
get '/blog/:id' do
#postID = params[:id]
#thePost = Blog.where(token: #postID)
render 'blog/index'
end
When I try to access <%= #thePost %> in my .erb file, I get:
#<Sequel::Mysql2::Dataset:0x007fdec1777318>
How do I access the actual data from the row here?
You are returning a relation, what you want to return is an actual record. To do this, use first at the end of your where call.
Blog.where(token: #postID).first
Blog.where(token: #postID) returns a list with all matches - even if the list contains only one elemen. OP is using the Sequel ORM instead of ActiveRecord (Hint is the return type Sequel::Mysql2::Dataset), therefore I would suggest to use first (or first! depending on your usecase) instead of where:
#thePost = Blog.first(token: #postID)
From the docs:
An alias for calling first on the model's dataset, but with optimized handling of the single argument case.

How to use trim in select query rails or in sql query

User.select(:name).group(:name).having("count(*) > 1")
this query works fine to select records having duplicate user name. But problem am facing is when there is space in name.
For example.
recoed1 = "Username"
record2 = "Username "
This are the two records but they are having same name, but above query consider them as different records because space is there in the second record. So while selecting I did not get this record.
Any solution using normal mysql query or rails will do.
OR
How I can strip or trim all the column data first from table using rails/mysql query. Then I can apply above query.
What i would do here is make sure your data is tidy in the first place.
You could put in a pre-validation method to call strip on usernames. You could do it like so
#in lib/trimmer.rb
module Trimmer
# Make a class method available to define space-trimming behavior.
def self.included base
base.extend(ClassMethods)
end
module ClassMethods
# Register a before-validation handler for the given fields to
# trim leading and trailing spaces.
def trimmed_fields *field_list
before_validation do |model|
field_list.each do |field|
model.send("#{field}=", model.send("#{field}").strip) if model.send("#{field}").respond_to?('strip')
end
end
end
end
end
Make sure this module is required, wherever you require things in lib in your config.
Now, you can say, in any models, like so (in this example i'm doing some other fields besides username)
class User < ActiveRecord::Base
include Trimmer
trimmed_fields :username, :email, :first_name, :last_name
...
So, that will fix you going forward. The remaining step is to tidy up your existing data. I would do this in a migration. (again, i'm doing some other fields as an example)
tables_and_cols = {"users" => %w(username email first_name last_name), "foos" => %w(bar baz)}
tables_and_cols.each do |table, cols|
cols.each do |col|
ActiveRecord::Base.connection.execute("update #{tablename} set #{col} = trim(#{col})")
end
end
Now, after doing this trim, you may have some duplicate usernames. You will need to decide how you are going to deal with that, since the records involved are no longer valid. If you haven't publically deployed this yet, ie if you don't have any active real users, then it doesn't matter: you can change the usernames to something else. But if you do have real people using it you will probably need to change the username for some of them and inform them. This is unavoidable if you want to maintain a situation where people can't have spaces at the start or end of their username.
You can use mysql's string functions:
User.select("lower(trim(name))").group("lower(trim(name))").having("count(*) > 1")

On Rails ActiveRecord show count based on parameter

I have some reporting methods throughout my app and in some cases I want to return the count (for a dashboard), and in others, return the full result set for viewing the details of a report.
What I'm wondering, is there a way to dynamically choose to show the count (instead of what I'm doing here):
def get_query_results(reporting_parameters, count_only = true)
#put together reporting details...
if count_only
MyModel.where(query).count
else
MyModel.where(query)
end
end
I considered setting a local variable to the result of my query parameter, and then call count, but that queries the database again (and even if it didn't it could increase memory usage).
Is there a way to do an effective way to do this in one query? This is one of several queries I have like this in my app, otherwise I wouldn't care. Also, I'd use a ternary, but the actual query conditions in my app are much longer than my example here and it makes it unreadable.
Suppose you are doing this:
#collection = get_query_results(...)
Then you can do this afterwards instead of inside of the action:
#collection.count
And if you like to call another method:
def total_number(collection)
collection.count
end
#collection = get_query_results(...)
no_of_records = total_number(#collection)

Can I create sperate queries for different views?

I'm learning sqlalchemy and not sure if I grasp it fully yet(I'm more used to writing queries by hand but I like the idea of abstracting the queries and getting objects). I'm going through the tutorial and trying to apply it to my code and ran into this part when defining a model:
def __repr__(self):
return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)
Its useful because I can just search for a username and get only the info about the user that I want but is there a way to either have multiple of these type of views that I can call? or am I using it wrong and should be writing a specific query for getting different data for different views?
Some context to why I'm asking my site has different templates, and most pages will just need the usersname, first/last name but some pages will require things like twitter or Facebook urls(also fields in the model).
First of all, __repr__ is not a view, so if you have a simple model User with defined columns, and you query for a User, all the columns will get loaded from the database, and not only those used in __repr__.
Lets take model Book (from the example refered to later) as a basis:
class Book(Base):
book_id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
summary = Column(String(2000))
excerpt = Column(Text)
photo = Column(Binary)
The first option to skip loading some columns is to use Deferred Column Loading:
class Book(Base):
# ...
excerpt = deferred(Column(Text))
photo = deferred(Column(Binary))
In this case when you execute query session.query(Book).get(1), the photo and excerpt columns will not be loaded until accessed from the code, at which point another query against the database will be executed to load the missing data.
But if you know before you query for the Book that you need the column photo immediately, you can still override the deferred behavior with undefer option: query = session.query(Book).options(undefer('photo')).get(1).
Basically, the suggestion here is to defer all the columns (in your case: except username, password etc) and in each use case (view) override with undefer those you know you need for that particular view. Please also see the group parameter of deferred, so that you can group the attributes by use case (view).
Another way would be to query only some columns, but in this case you are getting the tuple instance instead of the model instance (in your case User), so it is potentially OK for form filling, but not so good for model validation: session.query(Book.id, Book.title).all()