Rails 5 override dom_id helper method - ruby-on-rails

I have encountered a strange problem. We have a project, in which - for security reasons - we wanted to avoid displaying the id for most of the models. So, instead of having a dom id user_12, we wanted to use the serial (id="user_adsi94zurew"). First we gave it all manually (like in these two haml lines):
.user_line{id: "user_#{#user.serial}"}
= form_for #user, url: user_path(#user), id: {"edit_user_#{#user.serial}"}
It is just not nice, we didn't like it. Then I found the helper method dom_id, which I overrode in the application_helper the following way:
def dom_id(record, prefix = nil)
if record.respond_to?(:serial)
"#{dom_class(record, prefix)}_#{record.serial}"
else
super
end
end
It just worked fine for most of the situations, though I didn't have the time for a thorough testing, because - as our application grew larger - we needed to rewrite it. We changed to Rails 5.0.0.1 and started to sort our controllers in modules (folders). I copied the code to our new application_helper.rb, and I found it was not working (it returned the good old user_12). And then I found, it WAS working, and then again, it wasn't. And that was it: it was almost totally random, when or where it worked. I made some tests and found that, when not working, my code was not even run (so it was not that the condition evaluated to false), the method went straight into the original dom_id, as if mine hadn't existed at all!
Then I found some rules against my theory of random, but these were even stranger:
1.) In the file /app/views/company_manager/companies/edit.html.haml, as well as in the new.html.haml (same folder) it never works. My first thought was that it is due to the module CompanyManager.
2.) In the root (/app/views/landings/landing_page.html.haml) it worked. Then it didn't! But if I made any change in the helper method (adding a p 'here', or even an empty new line(!)), it worked again!
3.) In the partial /app/views/company_manager/regions/_region.html.haml (.region_line{id: dom_id(region)}) it didn't work, but in the ajax response update.js.erb (same folder) it did (at least occasionally). I had some tough ten minutes due to that, because I tried to update region line in the DOM with the following jQuery code: $('#<%= dom_id(#region) %>').replaceWith('<%= j(render #region) %>');, and since the dom_id returned region_6 first and region_hd5j8g6 second, jQuery couldn't find the line to update.
4.) Lastly, we use the old way to access helper methods from controllers (which means include ApplicationHelper in the ApplicationController). When the method returned company_31, I tried to reference it from the controller, and it raised a NoMethodError, whereas other helper methods from ApplicationHelper were still accessible.
Conclusion: it seems to me that as Rails merges the helper methods it is sometimes my method that overrides the original, and sometimes it is vice versa. If the server is already running, and I make some changes in my method, Rails seems to reload just application_helper.rb, so it is my method that is above the original (though it still has no effect on companies/edit or regions/_region)...
Any ideas? Other explanations? Something, I missed? Any hints?
(Note: With alternatives, thank you, I am well equipped. If there is no solution, I will rename my method to dom_id_serial and try to convince form_for to use it instead of dom_id, or anything. So I am interested in anything that overrides dom_id and works, or any reason why it does not, and nothing else.)
Thanks in advance!

Related

NoMethodError - Private method 'my_stats' called ... in rails console

Rails Console has been working fine until now. Suddenly I cannot call methods, neither class methods nor instance methods. But the app works fine.
Rails console
Catalog.my_stats
NoMethodError (private method `my_stats' called for #<Catalog:0x0000560a3796c9a8>)
If I try a method that does not exist it says "undefined method" instead of "private method", so it is able to see that the method exists. However - the method is NOT defined as private, and it has been working fine before. Built in Catalog.all works fine. Running on Ubuntu.
Example code for catalog.rb
def self.my_stats
puts "Hello!"
end
Update
I have another function in catalog.rb called string_to_array. This function responds well. And I can change it to a class function, and it works without problem. But if I create new functions they don't respond... also other, older functions responds well. Weird.
Short answer
The file had one "end" too much
Long answer I discovered that one of the functions above the troublesome ones had one "end" too much. Thanks to VS Code's vertical stripes I could identify that this particular "end" closed the class definition. This is why the functions were considered private, even without the word "private", because Rails gladly allowed them to be defined outside of the class. So the syntax checker did not consider this as an error. I also tried to add a lot of "end" words in the middle of the file, and in the end of the file, but the syntax was considered valid, still. This also explains why the GUI still worked, but the console had trouble accessing indirect "private" functions... maybe someone has a better name for these.. but now I've but them all inside the class where I think they belong. Thanks for your inputs.
Footnote
The stats code was tested temporarily in a particular branch that matched a spesific customer. The customer pasted this code into the catalog.rb file himself as a quick addon feature, and it worked for both of us. I then wanted this same code to be part of a new branch / version that was already in progress. I must have pasted it wrong, so that I did not get a fully functional checked-in version at my side - until now. Thats some time ago. So the "worked before - not now" was a little more tricky this time.

devise-two-factor how to get code valid for custom time to send over SMS/Email etc

I am trying to implement 2FA(two factor authentication) in my existing rails 4.2.10 application, I have configured many bits.
Issue I am facing is to get/retrieve a code which is valid for 5 minutes and send this code over to user on his defined phone number or email.
I did tried ROTP::TOTP.new(user.otp_secret).at(Time.now), guessing from gem's source code, which seems to work fine and give a valid otp_code in console, but in sessions_controler, as weird as it sounds, user.otp_secret is null, always...
I have posted an issue on the gem.
I don't think this can be bug, rather this is a functionality I want to build.
My stack:
Ruby: 2.4.2
Rails: 4.2.10
Devise: 4
attr_encrypted: 1.4(if it matters)
Additionally, I want to extend drift period(code acceptance time) to 5 minutes. I think that will be easy, but doing it for single code, not universally, or for all codes, this has me thinking for a while now.
My main issue is the first one, getting the code to send through SMS, this is a subproblem, which I think is doable, but if anyone has/had experience with this and can help, that will be great.
UPDATE: I updated attr_encrypted and restarted the system, it started working, also I realized there is a method current_otp in which devise_two_factor adds in the user model, so I started using that. BUT after a few minutes, it is also throwing the same issue of user.otp_secret being nil. Its getting weird...
UPDATE 2/Hacky solution: Weirdly enough, I had to add these 3 methods in user model and everything started working:
def encrypted_otp_secret
self[:encrypted_otp_secret]
end
def encrypted_otp_secret_iv
self[:encrypted_otp_secret_iv]
end
def encrypted_otp_secret_salt
self[:encrypted_otp_secret_salt]
end
As you can suspect, i got here by examining a behavior thatdoing user.encrypted_otp_secret was giving me nil while it was not, even after reloading user model. And doing user[:encrypted_otp_secret] was giving me the actual value.
It seems like a bug in attr_encrypted. I am not sure yet.
For anyone else that runs into this issue, I have found a next step needed to get the current_otp method to work. In the method pre_otp method call
> u = User.find_by(email: 'test#example.com')
> u.otp_required_for_login = true
> u.otp_secret = User.generate_otp_secret
> u.save!
and then you can call u.current_otp...
https://blog.tommyku.com/blog/integrating-two-step-two-factor-authentication-into-rails-4-project-with-devise/

Trying to test my nested Rails 5 form (mintest)

The actual nested form itself works fine, but i'd like to be able to have a test covering it to make sure I don't screw it up later. I also have more nested forms planned so I'd really like to figure this out.
I'm getting this failing test in my ListControllerTest:
"ListItem.count" didn't change by 1.
Expected: 1
Actual: 0
With this code:
https://pastebin.com/BRdtZW2T
Note that the "List.count" bit does pass. Again, this does actually work exactly as it's supposed to. I can create lists with lists items no problem when I actually submit forms on my app.
Lists are created with form_for
ListItems are created with fields_for
Figured it out. First off, I removed 'list_item_attributes' from the params being posted in my test. It seemed redundant, and I wasn't sure why I added it in there until I removed it, and my tests raised a different error:
TypeError: no implicit conversion of Symbol into Integer
app/controllers/lists_controller.rb:15:in `[]'
No idea what this was about, but some google searches turned up this:
http://billpatrianakos.me/blog/2013/09/29/rails-tricky-error-no-implicit-conversion-from-symbol-to-integer/
Which as it turns out is exactly my problem. So I added some extra square [] brackets to my params:[:list][:list_item], and presto it worked. See the revised pastebin as well as the article because that probably wasn't clear:
https://pastebin.com/uxRjsctK

ActiveModel::MissingAttributeError occurs after deploying and then goes away after a while

I have a Rails 3.0.9 app that, once it is deployed, suffers from a bunch of ActiveModel::MissingAttributeErrors that crop up causing 500s. The errors occur fairly randomly, sometimes a page will load, other times it won't, but the attributes are all existing attributes in the database and should be found.
The strange part is that after a while, the errors go away. Suddenly, they stop causing an issue.
I have searched about for a solution to this, but this error mostly occurs either when someone has done Model.all(:select => 'column_x,column_y') and are calling for column_z or when they are using cache_money. I am doing neither of these things.
Can anyone help?
You probably have a query that doesn't return all the columns (i.e. uses :select) and then cache_money; or some other ActiveRecord plugin uses an after_initialize callback, which executes whenever a new ActiveRecord object is created (i.e. when fetched from the database).
In that initialize callback, something tries to access or use an attribute that wasn't included in the :select. You'd expect this to return nil for that attribute, but an ActiveRecord::MissingAttributeError is thrown instead.
You can rescue ActiveRecord::MissingAttributeError like the article suggests, or patch the plugin(s) to use has_attribute?(:attribute_name) before they try to access or modify the attribute.
If you have been having this issue only directly after updating your database without any deploys or server restarts following, then what worked for me may work for you:
Run heroku restart and it should be fixed. Before the dyno restarts old data sometimes remains cached on the server, so starting it up again will scrub all of that data and prevent it from causing errors of that sort. Hope this helps.
I found an interesting take on this that resulted in the same error. In an attempt to reuse code we subclasses a presenters class with a presenters class that performed grouping to use in a graph view.
To simplify, it was something like:
class PostPresenter
def query
Post.where(...stuff....).includes(:wombat)
end
end
The the aggregator did something like the following to build a table of posts per day:
class AggregatePostPresenter < PostPresenter
def group_query
query.select('count(*) as cnt, date(created_at)').group('date(created_at)')
end
end
A call to "group_query" results in an ActiveModel::MissingAttributeError since, I think, the attempt to "includes" Wombat fails because "wombat_id" wasn't in the attributes included in the "select".
This is probably not your answer, however, since it happens regardless of whether or not cache is enabled.
I encountered this issue. Make sure your select: includes all fields referenced in your view, including any relationship IDs and any attributes called within your methods.
The missing attribute can be difficult to identify whenever your views and relationships are complex. The easiest way to debug this is to remove the select portion of your where clause and see if the query/scope/method runs correctly. If so, then add all of the attributes to the select and remove unneeded attributes one-at-a-time until you find the offending attribute.
A similar problem was annoying me when I was trying to make Ajax (actually angularjs) calls to populate an edit-in-place select fields.
I just wanted an id and name attributes to_json and kept getting the MissingAttributeError.
Realised I gotcha'd myself by having an as_json method in the model which is used for the main index and show calls on the model. Basically it was the as_json that was not seeing the attributes it expected.
#foo=Foo.select("id,name")
respond_to do |format|
format.json { render :json => #foo.to_json }
end
gave the error but
respond_to do |format|
format.json { render :json => { :foo=>#foo.as_json(:only=>[:id,:name]) } }
end
seems to be working. I was close to sussing it myself but I found a great explanation at.
http://jonathanjulian.com/2010/04/rails-to_json-or-as_json/
I fixed this by adding .to_json to the end of my controller render.
you need to add line
rescue ActiveRecord::MissingAttributeError
in your after_initialize() method of the model

Avoiding nil in Rails views

I'm sure this has been asked already, but I can't find the answer.
I have a Project model, which has a belongs_to relationship with my Client model. A client has a name, but a project doesn't necessarily have a client.
In my view, I've got code like this:
<%=h project.client && project.client.name %>
because if the project doesn't have a client then trying to access project.client.name causes a NoMethodError (nil doesn't have a method called name).
The question is, is it acceptable to have this kind of nil checking in the view, or should I be looking for another way around it?
Just use
project.client.try(:name)
I think its perfectly acceptable - this is view logic, you are more or less deciding whether or not to show portions of your view, based on whether there is data.
I run into this all the time, and yes it's annoying. Even when there is supposed to never be a nil, dirty data that I inherited sometimes triggers it.
Your solution is one way of handling it. You could also add a method to Project called client_name that displays the client name if it exists, but then you are linking the models together more than some people recommend.
def client_name
client && client.name
end
You could also make a helper method to do it, but you can end up writing a lot of them. :)
As mentioned by Skilldrick below, this is also useful to add a default string:
def client_name
client ? client.name : "no client"
end
You can use delegate in your Project class, so this way you will respect the Law of demeter which says that you should "talk only to your immediate friends".
project.rb
class Project
delegate :name, to: :client, prefix: true, allow_nil: true
end
So this way the project object will know where to ask about the client's name:
#You can now call
project.client_name
See more about delegate in the Rails documentation.
my hacky solution is to yield a block and rescue the error. Many would say using rescue as logic is very bad form. Just don't use this where you would actually need to know when something is nil and shouldn't be.
In application_helper.rb:
def none_on_fail
begin
return yield
rescue
return "(none entered)"
end
end
Then in the view:
<%= none_on_fail { project.client.name } %>
Then methods can be chained as deep as needed and it can be used on any method BUT it will cover up other potential problems with models/relationships/methods if they exist. I would equate it to taking out a splinter with a flamethrower. Very effective with painful consequences if used improperly.
I think these checks can usually be eliminated with a bit of thought. This has the benefit of keeping your view code cleaner, and more importantly, keeping logic out of the view layer, which is a best practice. Some templating engines don't allow any logic in the view.
There are at least a couple of scenarios. Let's say you have a show action that depends on an instance variable. I'd say if the record is not found the controller should not render the html, by redirecting or something else. If you have a loop in the view for an array, use #array.each do |a| end so that it doesn't evaluate if the array is empty. If you truly want an application default in the view, try loading it from a config file, e.g. #page_title || #{#APP_CONFIG['page_title']} (see Railscasts #85). Remember you may want to change these strings later, for example translating the UI.
Those are a couple scenarios where presence checks and usage of try can be avoided. I'd try to avoid them if possible. If you can't avoid them, I'd put the conditional checks in a view helper and add a helper unit test for it to verify (and document) both code paths.

Resources