Rails how to populate dropdown from DB. Herlp or ivar? - ruby-on-rails

My rails app has a number of forms that use select tags. To date, to populate these selects I've been initialising an ivar(s).
But it is becoming bit of a maintenance issue. It's easy to remember to populate an ivar when you are in a new or edit action. It's the create and update actions that are more problematic. Those ivars required by the form only need to be populated when there is an validation (or error) issue with the model.
Considering that the ivars are not needed when responding to xml or json requests, I'm questioning is there a better way.
Is this a case where I should be using helpers within my _form partial? To date I've always considered database access within a helper to be a code smell - but now I'm not so sure.
Just in case my post isn't clear:
def create
#user = User.new(params[:user])
# this is what i do now - but is there a better way
unless #user.save
load_form_required_ivars # here I'm unnecessarily
# hitting the db for json/xml requests
end
respond_with(#user)
end

You can use attr_accessor and attr_accessible to hold these values of the drop down you are populating. as you validate in the create and then pass their values back if there are validation issues.
I don't get the part about xml and json being different however, but this should work for that too.

Related

Ruby-on-rails: are there any other way to make page statefull except use session?

I am creating search form for model with some tabs, radiobuttons, dropbox, etc. I use index action for searching and sorting. Parameters for searching is persisted in params. It works while I stay in the same index view. But if I had to visit other page and then come back to search page again, params doesn't work and state is not persisted.
I know I can do it with session, but suppose I would have more search forms for another models and so all that params goes into session. Doesn't it make session messy?
So question is:
Is there other reasonable way to persist page state (but I don't want to put into database)
You might consider a class in your app to help manage data input/output, somewhat similar to ActiveRecord's database interface. You can store this data either in a session maybe memcache or redis server would be better. Here's an example.
class SearchParam
def initialize(id, model, query_string = nil)
#id = id
#model = model
#query_string = query_string
end
def save
# write #query_string to redis using `key`
end
def self.find(id, model)
instance = new(id, model)
instance.query_string = Redis.get(instance.key)
return instance
end
def query_string
#query_string
end
def query_string=(qstring)
#query_string=qstring
end
def key
"#{#id}-#{model}"
end
end
I forget the exact redis commands and syntax, but you'll get the idea if you've worked with it before - basic writing and reading. Memcache or session would also work. Then you can easily store the hash representation of the query string for a user/model combo and set your search params for use in the search form.
Easy to lookup based on a session id in the controller when they first get to the index page (if there are no params submitted)
#search_params = SearchParam.find(session.id, model).query_string
Or to save the new query_string when you return results based on the form submit in the index action
SearchParam.new(session.id, model, params).save
There is also localStorage (https://developer.mozilla.org/en/docs/Web/API/Window/localStorage) available in browser, but you have to use JS to access it.
Otherwise, I think it is okay to store it in session, unless you have like tens of different search forms.

Rails: Passing API JSON Response to View, without Model

Disclaimer: I'm doing something which may qualify for Code Smell of 2015 Award. Using rails 4.2, no javascript or anything like that.
I have a form into which users input their data. With this data I call a third-party API which will remain nameless. :)
I have no model, I'm not persisting anything. (Part of a larger app, not a one-pager.) Thus when faced with presenting the user with the response, I find myself stuck on how to render the data properly into a view. The response contains an array of hashes which I obviously intend to present the user.
I render the form into widgets/new, etc, create and process the request, etc, but then what?
I thought maybe I could make use of decorators to do my dirty work but not sure how to actually get the user off to the respective view. I don't care which view. Call it a widget_path.
WidgetsController < ApplicationController
def new
render :new
end
def create
# preparing request
...
# data = response, each_serializer, WidgetSerializer, root: false
# data = WidgetDecorator.new(render_serialized(response, WidgetSerializer))
# #data = WidgetDecorator.new(JSON.parse(response))
# redirect_to ??_path ... and take your #data with you
end
end
What do I do?
Your idea of Model is unfortunately corrupted by Rails itself (sorry).
A model is business logic not an ActiveRecord::Base (not necessarily). Controller methods shouldn't be big, ~5 lines long is probably the maximum with a ~100 lines max per controller file. Try to stick with this and it will automatically correct good chunck of code smells.
Anyway, you may handle this with a Model, as a PORO (plain old ruby object).
class MyApiResponse
attr_reader :myapikey
attr_reader :whatever
def initialize(myapikey, whatever)
#myapikey = myapikey
#whatever = whatever
end
def get
#_response ||= JSON.parse(run_api_stuff(myapikey))
end
end
So in controller you would do something like
def create
myapiresponse = MyApiResonse.new(myapikey, whatever)
#response = myapiresponse.get
end
Last but not least, you can't pass what you obtained through the API in the redirect. You are subject to HTTP limits so, you have a limit on GET params size, a limit on session and you can't redirect to a POST. You have 3 options
Best is store last api request for given user in the database and fetch it back through an ID (which will travel through the redirect)
Store it in session if request is really small (and you must ensure it is small!)
Perform the API request again after the redirect, horrible. Otherwise perform the API request only after redirect, not sure if this is an option though

Can I stop Rails from storing invalid attributes on the model after update is called?

I've been developing on Rails for awhile and can't believe I haven't ran into this problem - maybe I am missing something simple?
My edit page displays information about the model being viewed. The model's to_s method returns the name attribute in this case, which is displayed in breadcrumbs and the page header.
I have a validation that the name cannot be blank and a simple update method:
def update
#model.update(permitted_params)
respond_with #model
end
The default ActionController responder will render my edit page automatically when #model is invalid, which it does. But #model still retains the invalid attributes so my breadcrumbs and page header are blank as they both display model.name which is "".
I could solve this by
def to_s
name.presence || name_was
end
But this application will be fairly large and most of my models will follow this same view pattern with the header containing a model attribute that could be invalid. I feel like using this pattern in to_s on all of my models will be frustrating to keep up with.
My current solution is to define this method in my custom responder, which reloads the #model if it is invalid:
class ApplicationResponder < ActionController::Responder
def initialize(*)
super
#resource.reload if has_errors?
end
end
This works but now any invalid request has an extra call to the database when the model is reloaded. Probably not a big deal, but still a code smell in my opinion.
Is there something I can do to stop Rails from keeping invalid attributes on #model after update? I am using Rails 4.1.0beta1 and Ruby 2.1 and have tried on Rails 4.0.0 as well.
Why not also provide some client-side validations to prevent users from submitting invalid details in the first place?
If you provided validations that give the user feedback before the page is re-rendered, you won't have the problem of ever having to work out how to display that invalid data (since it sounds like you are displaying data on the edit page itself).
I would recommend you check out Parsley.js or other similar JS based client-side validation.
The kind of behavior you are seeing, where they are still "stored" in the model object is a good thing in general because it allows you to automatically repopulate the input fields in a form with those incorrect values, so that a user can see what they entered wrong and change it.
If you are wanting to maintain certain 'correct' values statically on the page, like in a breadcrumb, I would copy those values off into their own variables and track them separately. For the breadcrumb on the application I work on, we actually store the link history in the browsers IndexedDB, and don't rely on what the current value of an object in the database is.
From my experience, most of the time the identifying attribute of an object, like it's name, isn't editable. If it is, I tend to make the name of my page something a little more general, like 'Edit Profile,' instead of 'Edit ', or 'Edit Organization' instead of 'Edit Name_of_Organization.'
If you really want to keep rendering the original name, I would just save that and track it through hidden HTML inputs. What you're doing with the to_s method isn't foolproof: if a user enters an invalid name that's not blank, you're going to render that invalid name.

Rails 2.3.4 Persisting Model on Validation Failure

I have a standard html form post that is being persisted and validated with a rails model on the server side.
For the sake of discussion, lets call this a "car" model.
When
car.save
is invoked the validators fire and set a list of fields in error within the model.
How best can I get an object back that has the fields whose validation failed removed, i.e. such that I don't have to delete the fields on the form the user entered correctly?
The validation patterns they use are great but how do I get a hook to when the validation fails such that I can delete the bogus entries from the model?
I could see manually iterating over the errors list and writing a big case statement but that seems like a hack.
In your controller, render the new action from your create action if validation fails, with an instance variable, #car populated from the user input (i.e., the params hash). Then, in your view, add a logic check (either an if block around the form or a ternary on the helpers, your choice) that automatically sets the value of the form fields to the params values passed in to #car if car exists. That way, the form will be blank on first visit and in theory only be populated on re-render in the case of error. In any case, they will not be populated unless #car is set.
EDIT:
Don't do this (see comments) but if you must here is how:
I ended up using a call to
#car.invalid?
inside of a loop to remove the invalid entries:
params[:car].each do | key, array|
if #car.errors.invalid?(key)
#car[key.to_sym] = ''
end
end
render :action=> 'new'
Note the use of the render here vs. redirect. Getting error messages to persist from the rails model validation across a redirect appears tricky, see:
Rails validation over redirect
I still feel like there should be a helper method for this or a better way.

In ruby on rails what code would ask whether a form passed all validation or not?

I want to have my form forward to recaptcha but only after form has passed all validation. How would I achieve this before users details are saved to DB?
def create
#user = User.new(params[:user])
respond_to do |format|
if #user.save
#to recaptcha, but before save and only after sign up form passes validation
else
format.html { render :new }
format.js { render :form_errors }
end
end
end
Have a good look at The definitive guide to form-based website authentication and ask yourself if you really need a captcha.
Besides that, you can use Callbacks :after_validation, before_save, around_save, after_save, before_create, around_create, after_create, before_update, around_update, after_update to handle stuff still inside your transaction.
The way to call one of these callbacks is to simply declare them in your model
If you need to use a captcha however, I would do this with javascript and ajax, to append it to your form before the user sends it.
You should not do this in the controller after recieving a post of the form, since you will have to:
Store the filled form values in the session after validation (dont save)
Redirect the user to a captcha page (which will make any user confused)
Check the captcha multiple times before it passes (they are quite unreadable)
Get the model out of the session (which you have no idea of which one it is)
Call save on the model to actually write it to your DB.
So basically you avoid starting a transaction before the captcha is passed.
Validation lives in the model, you could simply do this in the controller:
#user.valid?
and then do your recaptcha stuff.
Another solution is to use callbacks such as: before_save or before_create but only if recaptcha could be accessed in model (which I doubt).
This Railscast has all you need to know about multistep forms. The episode covers validation and moving back and forth between steps. http://railscasts.com/episodes/217-multistep-forms
It sounds like your form has two steps, the first being where they enter in all their information, and the second being just a captcha entry.
Now, in my opinion you should just roll the captcha into the main user entry form and keep it all to a single page rather than having a two step process, I've done both before and having the captcha be part of the same form is much, much easier and less complex. Having everything in a single form allows you to have all of your logic consolidated (mostly) into a single controller action. There may be logic you can abstract out of the controller into a helper method, like the verification of the captcha, which will make your controller action that much less complicated. The last thing you want to do is over-complicate your action logic.

Resources