Conditionally Omit/Hide Button in Rails - ruby-on-rails

My first time asking. I'm in an internship so I'm not that proficient in Rails. Currently I'm working in a code that isn't mine, it has no automated tests and no documentation whatsoever, so I spent lots of time trying to understand what the functions does before actually writing some code.
My question:
What I'm trying to accomplish is to hide/omit a button, under a set of conditions.
My research:
Already searched everywhere but with no luck to my specific problem. Tried the solutions here: How to disable all form_for input fields in Ruby on Rails app? and here: In Rails edit action form, hide a form field. Also read rails documentation about form helpers: https://guides.rubyonrails.org/v4.2.10/form_helpers.html
The problem:
There is an object called school that is divided into Schools and SchoolGroup, theoretically schools that have children should be SchoolGroup. I'm trying to omit the Delete button to every SchoolGroup that have Schools associated with it.
If I put like this nothing happens
<% if school.children.length < 1 %>
<%= button_to 'Delete', school, :data => {:confirm => 'Are you sure you want to delete this school?'}, :method => :delete, :class=>"btn btn-sm btn-delete" %>
<% end %>
However if I put like this
<% if school.children.length > 0 %>
every button disappears!
So, I'm inferring that every school probably have children, even though it shouldn't and I need to ask for a different condition. Or should I try a controller-wise solution? If so, what can I do?
I'm using ruby 2.2.10 and rails 4.2.10.
Thank you all for your time and any help :)

The school.children.length returns the size of the collection. So,new child is also counted.
length() Returns the size of the collection calling size on the target. If the collection has been already loaded, length and size are equivalent. If not and you are going to need the records anyway this method will take one less query. Otherwise size is more efficient.
You can use school.children.count to count the records that persisted in the database. This method call will fire a database query.

Related

Can access ActiveRecord object, but not its attributes

I'm rendering a view in Rails using form_for and nested_form_fields. Here, #procedure_step is a record that has_many :procedure_step_actions, each of which belongs_to :error, which is a ProcedureError that has (among some relations to other models) an integer :code that I'm trying to access and print out to the page. Here's my template:
<%= form_for #procedure_step do |f| %>
<%= f.nested_fields_for :procedure_step_actions do |act| %>
<%= act.object.error.code %>
<% end %>
<% end %>
When I run this, I get undefined method 'code' for nil:NilClass. Okay, so my relations are messed up and I can't access act.object.error, right? Changing my template to display that instead yields #<ProcedureError:0x0000000ece02a8>, which is what one would expect of a functioning relation. Dumping its contents to the screen using debug shows all the attributes of the record, including code, but I still can't access it with the original template! Clearly act.object.error is not nil, so Rails telling me that act.object.error is nil doesn't make any sense to me.
Frustrated, I tried to work around the problem by using act.object.error.to_json. This printed the correct JSON for the record with all its attributes. Using JSON.load() on this gave me a correct Hash of all the attributes, but using [:code] to try to access the code gives me undefined method '[]' for nil:NilClass. Again, I know that object isn't nil, but Rails still refuses to allow me to access it.
Running out of ideas, I tried to use regular expressions to pull the code out of the raw JSON string. /"code":([0-9]+)/.match(act.object.error.to_json) returned #<MatchData "\"code\":69" 1:"69">, which is right. I used [1] to try to access the code number that was matched, but again I got undefined method '[]' for nil:NilClass.
Enough with ActiveRecord, I thought to myself. I decided to turn to raw SQL queries. I got the ID of the error in question using act.object.error_id, then printed that to the screen first to make sure I could access it. Luckily, I could. Then I inserted it into my SQL query with "... WHERE id = #{act.object.error_id}". I refreshed the page again and was greeted with a SQL error. It showed the final SQL query string I had generated, but it ended with WHERE id =. The ID of the error didn't get added to the string. ProcedureError.find(action.object.error_id) gave a similar error.
I'm totally out of ideas. What could possibly be preventing me from accessing one simple integer in so many different ways?
There are at least a couple of issues here. The first is that you probably want to be using fields_for, rather than nested_fields_for, if you're using 4.x.
The second is similar to what the first answer has indicated. You have a nested fields form, which allows you to nest one level in, but you are trying to nest two levels in. By addressing your law of demeter violation you should be able to make some more progress.
Debugging things like this you can get more information by throwing in a binding.pry or byebug right in your erb.
<%- binding.pry %>
Then reload the page. Your server will be stopped at that point in your code and you can play with variable values to learn more about what's going on.
One thing I can see right off the bat is you are violating Law of Demeter here
act.object.error.code
The form object obviously has to stay but you can delegate access to the subobjects by making a method on the procedure_step which can help with handling nulls, and other error cases.
Try delegating that first as I'm not sure if the scope that is created by nested_forms_for will allow the ActiveRecord::Relation object to perform properly. I'll double check locally.
A delegation might look like the following
class ProcedureStepActions
belongs_to :error
def error_code
#error.code
end
end
EDIT:
Other things that might be helpful are the version of Ruby and Rails you are using and any other additional gems or libraries.

Rails / design array issue

In my 'releases' show view I have the following code:
<% i = #release.id %>
<%= link_to image_tag('next.png'), release_path(i+1), :class => "editRelease" %>
Which takes the user to the next result in the releases table.
I am hoping to only display this 'next' button if an item exists in the array whose id value is one greater than the current release.
Happy to re-write this section of the view / place code in model,controller,helper definition.
Just trying to learn the correct rails way to solve this issue!
Thank you!
Records can be deleted, leaving gaps in your range of ID's, as such using a direct id+1 could point to a null record.
Instead you should define a next method in your model that safely returns the next object. If you don't have any ordering then you can use a query like the one posted in this answer.

Rails STI, subclasses don't show up unless used?

I am using Rails STI to model a complex domain. In a view, I am listing all subclasses with:
> <% StateDescription.subclasses.each do |state_description| %> <li>
> <%= state_description.to_s %> </li> <% end %>
With the intent to eventually make each label of the subclass a link to the individual "index" page for just instances of that subclass.
This saves me from having to have a list of all possible subclasses myself (and updating it later if I add more).
However, I have noticed that only those subclasses I have actually loaded from the database show up in the list. If I say StateDescription.all, then all existing subclasses show up (but none that have no instances yet). If I instead ask for all of a particular subclass, then only that subclass show up.
I imagine this is part of the "lazy loading" I have heard about. Is it? It SEEMS like the problem is that if I don't grab a particular "type" from the database, even if I have a model for it, it may as well not exist?
Is there a way around this, or am I doomed to have to write out a link for every single subclass I create?
Edit:
On the console, if I type
StateDescription.subclasses.count
I get 0.
If I then do StateDescription.all and THEN StateDescription.subclasses.count, i get 14.
After doing some digging, it seems your hypothesis about subclasses not showing up due to lazy loading appears to be correct. Since you're running your application in development mode, all your classes are not loaded until they are specifically called. In production, you would not have this problem since everything is loaded at once and cached.
One way to get around this problem, according to this post, is to do something like this:
[Subclass1, Subclass2, Subclass3] if Rails.env == 'development'
You could put this at the top of your controller so that it loads the instant the controller classes is loaded, or in a before filter.
Not tested!
StateDescription.select("distinct type").map { |sd| Kernel.const_get(sd.type) }
The easiest way to do this is eager_load! in development.
Something like:
<% Rails.application.eager_load! if Rails.env.development? %>
<% StateDescription.subclasses.each do |state_description| %>
<li><%= state_description.to_s %> </li>
<% end %>

Improving performance on loading a long list of facebook friends

As part of my rails project, I have a feature that allows a user to issue invites to their FB friends. I'm using fb_graph for the API calls, and the below is a sampling of the code from the controller when the user hits the invite page.
This operation gets really expensive. I've seen it take longer than 30 seconds for users with upwards of 1000 friends. Also, this code re-executes each time a user hits the invite page. While a user's FB friends list isn't exactly static, it should be okay not recalculating this on every request.
So what I want to do is improve on this code and make it more efficient. I can think of a few different potential ways to potentially do this, but what makes the most sense in this case? This is a bit more open-ended than I usually like to ask on SO, but as I'm still relatively new to programming, I'm curious just as much on what you would/wouldn't do as much as how to do it.
Here are some ideas on optimizations I could make:
1) Only optimize within a session. The code executes the first time the page is hit and will persist for the rest of the session. I'm actually not sure how to do this.
2) Persist to the database. Add a column to the user table that will hold the friends hash. I could refresh this data periodically using a background job (perhaps once a week?)
3) Persist with caching. I'm not even exactly sure what's involved with this or if this is an appropriate use-case. My feeling is that option 2 requires a lot of manual maintenance, and that perhaps there's a nice caching solution for this that handles expirations, etc., but not sure
Other ideas? Appreciate your thoughts on the options.
# fetch full array of facebook friends
#fb_friends = current_user.facebook.fetch.friends
# strip out only id, name, and photo for each friend
#fb_friends.map! { |f| { identifier: f.identifier, name: f.name, picture: f.picture }}
# sort alphabetically by first name
#fb_friends.sort! { |a,b| a[:name].downcase <=> b[:name].downcase }
# split into two lists. those already on vs not on network
#fb_friends_on_network = Array.new
#fb_friends.each do |friend|
friend_find = Authorization.find_by_uid_and_provider(friend[:identifier], 'facebook')
if friend_find
#fb_friends_on_network << friend_find.user_id
#fb_friends.delete(friend)
end
end
UPDATE # 1
Adding a bit more on an initial experiment I conducted. I added a column to my user table that holds the #fb_friends array (post-processing the transformations shown above). Basically the controller code above is replaced with simply #fb_friends = current_user.fbfriends. I thought this would cut the load significantly as there is no more call to Facebook, not to mention all the processing done above. This did save some time but not as much as expected. My own friends list took about 6 secs to load on my local machine, after these changes its down to 4 secs. I must be missing something bigger here on the load issue.
UPDATE #2
Upon further investigation, I learned that almost half of data transfer was attributed to the form I was using for the "Invite" buttons. The form would load once for each friend and looked like this:
<%= form_for([#group, #invitation], :remote => true, :html => { :'data-type' => 'html', :class => 'fbinvite_form', :id => friend[:identifier]}) do |f| %>
<%= f.hidden_field :recipient_email, :value => "facebook#meetcody.com" %>
<div class = "fbinvite btn_list_right" id = "<%= friend[:identifier] %>">
<%= f.submit "Invite", :class => "btn btn-medium btn-primary", :name => "fb" %>
</div>
<% end %>
I decided to remove the form and inside place a simple button:
<div class = "fbinvite_form" id = "<%= friend[:identifier] %>" name = "fb">
<div class = "btn btn-small">
Invite
</div>
</div>
I then used ajax to detect a click and take the appropriate actions. This change literally cut the data transfer in half. Before loading about 500 friends took ~650kb, now its down to ~330kb.
I'm thinking I will go back and try what I tried in Update # 1, to do pre-processing. Combined I'm hoping I can get this down to a ~2 sec operation.
UPDATE #3
I ended up installing Miniprofiler to learn more what could be slowing this operation down and learned that my for loop above is terrible inefficient as it makes a trip to the DB on every friend. I posted in a separate question and got help to reduce the trips down to just one. I then went ahead and implemented the pre-processing I mentioned in Update #1. With all these changes, I have it down to ~700ms which is remarkable considering it was taking +20 secs plus before going down this path!
If you can run some query's in parallel I'll suggest you to take a look at: The futoroscope gem.
As you can see by the announcing blog post it tries to solve the same problem of making simultaneous API query's. It's seems to have pretty good support and good test coverage.

Rails 3 and Memcached - Intelligent caching without expiration

I am implementing caching into my Rails project via Memcached and particularly trying to cache side column blocks (most recent photos, blogs, etc), and currently I have them expiring the cache every 15 minutes or so. Which works, but if I can do it more up-to-date like whenever new content is added, updated or whatnot, that would be better.
I was watching the episode of the Scaling Rails screencasts on Memcached http://content.newrelic.com/railslab/videos/08-ScalingRails-Memcached-fixed.mp4, and at 8:27 in the video, Gregg Pollack talks about intelligent caching in Memcached in a way where intelligent keys (in this example, the updated_at timestamp) are used to replace previously cached items without having to expire the cache. So whenever the timestamp is updated, the cache would refresh as it seeks a new timestamp, I would presume.
I am using my "Recent Photos" sideblock for this example, and this is how it's set up...
_side-column.html.erb:
<div id="photos"">
<p class="header">Photos</p>
<%= render :partial => 'shared/photos', :collection => #recent_photos %>
</div>
_photos.html.erb
<% cache(photos) do %>
<div class="row">
<%= image_tag photos.thumbnail.url(:thumb) %>
<h3><%= link_to photos.title, photos %></h3>
<p><%= photos.photos_count %> Photos</p>
</div>
</div>
<% end %>
On the first run, Memcached caches the block as views/photos/1-20110308040600 and will reload that cached fragment when the page is refreshed, so far so good. Then I add an additional photo to that particular row in the backend and reload, but the photo count is not updated. The log shows that it's still loading from views/photos/1-20110308040600 and not grabbing an updated timestamp. Everything I'm doing appears to be the same as what the video is doing, what am I doing wrong above?
In addition, there is a part two to this question. As you see in the partial above, #recent_photos query is called for the collection (out of a module in my lib folder). However, I noticed that even when the block is cached, this SELECT query is still being called. I attempted to wrap the entire partial in a block at first as <% cache(#recent_photos) do %>, but obviously this doesn't work - especially as there is no real timestamp on the whole collection, just it's individual items of course. How can I prevent this query from being made if the results are cached already?
UPDATE
In reference to the second question, I found that unless Rails.cache.exist? may just be my ticket, but what's tricky is the wildcard nature of using the timestamp...
UPDATE 2
Disregard my first question entirely, I figured out exactly why the cache wasn't refreshing. That's because the updated_at field wasn't being updated. Reason for that is that I was adding/deleting an item that is a nested resource in a parent, and I probably need to implement a "touch" on that in order to update the updated_at field in the parent.
But my second question still stands...the main #recent_photos query is still being called even if the fragment is cached...is there a way using cache.exists? to target a cache that is named something like /views/photos/1-2011random ?
One of the major flaws with Rails caching is that you cannot reliably separate the controller and the view for cached components. The only solution I've found is to embed the query in the cached block directly, but preferably through a helper method.
For instance, you probably have something like this:
class PhotosController < ApplicationController
def index
# ...
#recent_photos = Photos.where(...).all
# ...
end
end
The first instinct would be to only run that query if it will be required by the view, such as testing for the presence of the cached content. Unfortunately there is a small chance that the content will expire in the interval between you testing for it being cached and actually rendering the page, something that will lead to a template rendering error when the nil-value #recent_photos is used.
Here's a simpler approach:
<%= render :partial => 'shared/photos', :collection => recent_photos %>
Instead of using an instance variable, use a helper method. Define your helper method as you would've the load inside the controller:
module PhotosHelper
def recent_photos
#recent_photos ||= Photos.where(...).all
end
end
In this case the value is saved so that multiple calls to the same helper method only triggers the query once. This may not be necessary in your application and can be omitted. All the method is obligated to do is return a list of "recent photos", after all.
A lot of this mess could be eliminated if Rails supported sub-controllers with their own associated views, which is a variation on the pattern employed here.
As I've been working further with caching since asking this question, I think I'm starting to understand exactly the value of this kind of caching technique.
For example, I have an article and through a variety of things I need for the page which include querying other tables, maybe I need to do five-seven different queries per article. However, caching the article in this way reduces all those queries to one.
I am assuming that with this technique, there always needs to have at least "one" query, as there needs to be "some" way to tell whether the timestamp has been updated or not.

Resources