sharing variables between controller an view in a rails app - ruby-on-rails

in my controller I am collecting data (into a hash) like this (note that I do not have a BillingAddress model in my app, #billing_address is standard ruby hash
#billing_address = params[:billing_address]
my view is laid out like this
<%= text_field_tag 'billing_address[phone]' %>
I want to show the previous value that user entered (in case of errors), like this:
<%= text_field_tag 'billing_address[phone]', #billing_address['phone'] %>
however, this gives me an exception saying I am trying to access nil, ideas?

I guess params[:billing_address] is nil.
Try to assign empty hash if it is.
#billing_address = params[:billing_address] || {}

This is not the Rails way of showing previous values in case of error. Check this screencast to get idea on how to handle errors better way.

Related

Rails: How can I load new values into a form on edit but only persist them on save?

I've got a Rails 4 app with a service that loads required objects on a new/edit document action, and this is a method from it:
def template_variables
if #document.template_variables.any?
TemplateVariable.where(id: document_vars).each do |v|
next unless User.method_defined?(v.name.to_sym)
v.update_attribute(:text, #user.send(v.name.to_sym)) # problem line, persists the change when i don't want to
v.text = #user.send(v.name.to_sym) # also a problem, doesn't update the value in the form at all
end
else
TemplateVariable.where(id: master_vars)
end
end
I need a solution for the two problem lines (they are just two things I've thought of, and they aren't supposed to both be there but I've included both for the sake of my problem).
The first updates and persists the change to the model which is behaviour I don't want. The second line doesn't do anything, where logically it seems like it should replace whatever text was in that variable in the form with #user.send(v.name.to_sym). It appears to do nothing.
Is there are solution to this problem that I'm unaware of?
Bonus points if there's a way to list the fields with new values to display in a flash[:notice].
Update now with relevant form code.
<%= v.input :text, as: :string, input_html: { value: v.object.text } %>
Setting the value/vs not setting it doesn't change anything either.
If you've got an instance variable that you're passing from your controller, you can set (but not save) values on that variable which will be available in the view.
For example, you can set the first and last name of a new user by passing in arguments or setting the attributes in the controller:
#UsersController
def new
#user = User.new(first_name: 'John')
#user.last_name = 'Smith'
end
In the view
#users/new.erb.html
<%= #user.first_name # will be John %>
<%= #user.last_name # will be Smith %>
But in your case, you're using update_attribute, which saves the record changes. Instead, you should be creating an instance variable, setting (but not saving) the values on that, and using that in the view/form
e.g.
#varibales = TemplateVariable.where(id: document_vars)
#variables.each do |variable|
#change what you want here
variable.foo = 'bar'
end
And then reference your #variables object in the view/form.
In saying all of that, you should strive to only use one instance variable in your controller, consider using form objects, if you need to pass multiple values from the controller to the view.
As for the flash notice, you can display whatever you like as a flash notice by setting that in the controller, assuming you've got your view setup to display flash notices as shown here
flash[:notice] = "The value of #{#variables.first.foo}"

rails text_field / text_ara empty string vs nil

I'm not even sure if I have a problem, but I just don't like that my text_fields and text_areas get saved in the db as empty string instead of nil.
I'm playing with the null object pattern and just realized if I create a profile but don't fill in the location field and then call <%= #user.profile.location.upcase %> then it doesn't blow up since location is a string even it it's empty.
Is this the rails convention to have it this way? If so, then why? It's weird a bit since let's say there is a number_of_pets attr on the profile and then I try to call something like
<% if user.profile.number_of_pets.odd? %>
<%= #user.profile.first_name %> has odd number of pets
<% end %>
then it blow's up since I can't call nil.odd?.
form as usual, so it will saved as empty string if not filled
<%= form_for #profile, url: user_profile_path do |f| %>
<%= f.label :city %>
<%= f.text_field :location, class: 'form-control' %>
......
The easiest work around is to use a gem like "strip_attributes" found here: https://github.com/rmm5t/strip_attributes
A custom workaround could be done by adding a before_save callback in your model that takes any values that are blank and sets them back to nil.
In your model for example:
before_save :my_nil_maker_method_lol
[...]
def my_nil_maker_method_lol
if self.whatever_attribute.blank?
self.whatever_attribute=nil
end
end
Update:
Keeping blank fields from being saved could be done several ways such as described above or even deleting blank params in your controller before they hit the database.
The Rails way, when maintaining database integrity, is to always keep this logic inside your model. It makes it much easier to maintain and leaves much less room for surprises like if you were to modify the incoming parameters somewhere else.
As far as how it should be done in the model is really just a matter of what you as the developer expect to get for input. You can add a callback as shown above which maintains your db as you see fit or you can add a validates_presence_of validation that will return an error to the user if that field is left blank.
If you're asking whether you should be keeping empty strings from being inserted into the database at all, it is really up to you as the developer since there may be instances where you might want that information but in this case it sounds as though you're looking to restrict empty strings.
I am speaking from what I observed, and may not necessarily the Rails-convention.
When we do rails g post title content:text for example, you may remember that it does not have default: '' for both title(string) and content(text). This is already a hint to me that there is no Rails-convention regarding this, or specifically speaking every attribute by default is allowed to be NULL or nil.
The advantage of using NULL is that you can identify which records have those attributes set up.
Let's say we have an API server. If a client creates a post to our API server, we know which attributes are intended to have a value. Let's say something like the following:
client-1's POST params:
post: {title: 'Foo bar'}
if NULL-allowed, will create a Post(title: 'Foo bar', content: nil)
if default: '', will create a Post(title: 'Foo bar', content: '')
client-2's POST params:
post: {title: 'Foo bar', content: ''}
if NULL-allowed, will create a Post(title: 'Foo bar', content: '')
if default: '', will create a Post(title: 'Foo bar', content: '')
From above, notice that if we have default: '', then we cannot know if the client is actually intending to have an empty content value, because for both client-1 and client-2, the resulting content value for the post will have '' (empty) anyway. But if we have NULL-allowed attributes, then we can still identify if the client intended to not have a content value by not passing in the attribute in the parameters. This is an important use-case.
Depending on your project and the attribute's purpose, you may either use NULL-allowed or set default empty string for that attribute.
Now, the one main problem with NULL-allowed as you have encountered is that you cannot guarantee that every value will be a String, therefore your <%= #user.profile.location.upcase %> will raise an error in case location is NULL or nil.
This can be a little annoying especially if you have a chain of methods like your example
<%= #user.profile.location.upcase.downcase %>
This will be a problem if #user.profile.location is nil, because you'll have to gracefully ensure that it won't raise an error. And this will also be a problem if #user.profile is nil (let's just say you are allowing #user.profile to be nil). And normally, you'll do something like the following to make this work:
<% if !#user.profile.nil? && !#user.profile.location.nil? %>
<%= #user.profile.location.upcase.downcase %>
<% end %>
That if condition can still go on very long as you have longer chained methods to gracefully ensure it won't raise any error.
Using .try() will potentially "clean" this up. I use this a lot of times especially in template files. Solution will be cleaner like below, albeit potentially confusing for those who do not know:
<%= #user.profile.try(:location).try(:upcase).try(:downcase) %>
If either .location or .upcase is nil, it will return nil, and not raise any undefined method ... for NilClass anymore.

Serialized attributes not evaluating properly with form helpers

I'm rendering a form with serialized attributes. The attribute serialization works fine under normal usage, but now i'm trying to serialize a hash. The view code looks like the following
<%= #item.seasonality_of_sales_pct.each do |key, value| %>
<%= eval("f.label :'seasonality_of_sales_pct[:#{key}]'") %>
<%= eval("f.text_field :'seasonality_of_sales_pct[:#{key}]'") %>
<% end %>
The error I'm getting is undefined method 'seasonality_of_sales_pct[:January]' for #<Item:0x007f01083edd38>. However, the line that is throwing the error is the second eval. The first eval evaluates just fine. I'm baffled as to why this might be happening.
In the controller, I am setting up an attribute like the following
#item.seasonality_of_sales_pct = {January: nil, February: nil, March: nil, September: nil}
Another question that could maybe be answered in the comments is: How bad does this code smell? I'm not sure how the rails community feels about metaprogramming like this. It hurts me a bit to do it, but seems to work most of the time
When you use form_for and then use f.text_field :some_attribute_name then the object you are building the form for (in your case #item) mush have an attribute named some_attribute_name.
You get this error because #item has no attribute or method named seasonality_of_sales_pct[:January]
I also would point out that there is no reason to use eval in your form, it is a serious security risk, as code can be injected.
I wanted to be a bit more thorough than Khaled's answer, which was sort of right. The reason that the first eval statement didn't cause the error was because f.label doesn't care what you give it. <%= f.label :fake_stuff %> will just create a label called Fake Stuff. I'm still not quite sure why the attribute didn't work. If I had f.text_field :seasonality_of_sales_pct, I got a text box full of my hash. Also, I got the labels to display the correct values.
I absolutely did not need to use evals here (I can hope it was only a moment of weakness). Just do
<%= f.text_field :'seasonality_of_sales_pct[:"#{key}"]' %>

How it's possible to send an array from one view to the next

I have a Controller with the function getAccounts where I look for certain accounts. My idea is to first show the number of results and then send the result array to the next function called showAccounts which generates the view. First of all I declared the result array as an instance variable. Then I tried to send with a form tag. It does not work ... Has anyone an idea?
def getAccounts
filter = '(uid='+params[:id]+')'
attrs = ['*']
#accounts=Array.new
conn = LDAP::Conn.new($HOST, $PORT)
conn.bind('cn=admin, dc=cippool-mb, dc=rwth-aachen, dc=de','DLPins!')
conn.perror("bind")
begin
conn.search($base, $scope, filter, attrs) { |entry|
setAttributes(entry)
}
rescue LDAP::ResultError
conn.perror("search")
exit
end
conn.perror("search")
conn.unbind
end
def showAccounts
end
The view where I send the data.
Es wurden <%= #accounts.size %> Accounts gefunden.
<%= form_tag :action => "showAccounts" do %>
<%= hidden_field_tag "accounts", #accounts %>
<%= submit_tag "Anzeigen" %>
<% end %>
I can also paste the view where I need this array, but I dont't think it's relevant for this question. I use Rails 3.2.7 and Ruby 1.9.2p0
If you want to pass some large amount of data between separate requests I would suggest using session, it's designed for such things.
If you debug(#accounts) you'll see what it passes -- something like <#0x7187237 Array> which is not what you want!
If you really want to pass in the accounts array, you'll need to serialize it to a text format to put in a hidden field. That's going to probably be a HUGE chunk of data though if #accounts is large.
That said, you could dump it to YAML or JSON, or use one of the serialization functions in Ruby or put it into a custom text format of your own (not recommended). Keep in mind then that you need to deserialize on the next page before you use it.
I'm assuming part of the wanting to pass it to the next step is to avoid an expensive LDAP request. You might want to look at putting in a lightweight cache -- redis for example -- to temporarily store the requests.

What to use instead of find_with_ids( )?

The code below displays peferctly what i want to accomplish (show store name and item name).But when i subtitute #onedeal=#deal.find_with_ids(62) with #onedeal=#deal.find(params[:id]) i get an error Couldn't find Deal without an ID.What method should i use to fetch deal ID dynamically?The relationship between the Deal and store model is has many :through.
controller
#deal=#city.deals
#onedeal=#deal.find_with_ids(62)
#store=#onedeal.stores.first(params[:store_id])
view
<% #deal.each do |deal| %>
<%=deal.item_name %>
<%end%>
<%=#store.store_name %>
That error means that params[:id] is empty. Check your params hash to see what it contains, and verify that your action is getting the input it expects.
You are absolutely using the .find method as intended, so I don't think that's the issue.
What about
Deal.find(params[:id]) rescue nil

Resources