Ruby MVC and text_field - ruby-on-rails

I have this code which I don't really understand:
app\controllers\look_controller.rb
class LookController < ApplicationController
def at
#data_hash = params[:cruncher]
#cruncher = Cruncher.new(#data_hash[:crunch])
#data = #cruncher.crunch
end
def input
end
end
app\models\cruncher.rb
class Cruncher
attr_reader :crunch
attr_writer :crunch
def initialize(data)
#crunch = data
end
end
app\views\look\input.rhtml:
<html>
<head>
<title>Using Text Fields</title>
</head>
<body>
<h1>Working With Text Fields</h1>
This Ruby on Rails application lets you read data from text fields.
<br>
<%= start_form_tag ({:action => “at”}, {:method => “post”}) %>
Please enter your name.
<br>
<%= text_field (“cruncher”, “crunch”, {“size” => 30}) %>
<br>
<br>
<input type=”submit”/>
<%= end_form_tag %>
</body>
</html>
I do not understand what is the relationship between <%= text_field (“cruncher”, “crunch”, {“size” => 30}) %> and the model. What do text_fields attributes cruncher and crunch have to do with the model?
As I understand the params is a special hash that stores the data from the user, and by using #data_hash = params[:hash] inside the controller we store that data.
But what about this #cruncher = Cruncher.new(#data_hash[:crunch]), why do we now use #data_hash[:crunch]?
Why not just #data_hash?
Thanks.

if you look at he html produced by the input view, you'll see something like this for the text field:
<input type="text" name="cruncher_crunch" value="cruncher[crunch]" size="30" />
this means that the params hash, created when the form is submitted, and sent to the LookController#at method will be formatted like this:
{cruncher: {crunch: 'somevalue'}}
which is exactly the format that the Cruncher.new(#data_hash[:cruncher]) expects.

Its not that strange that you don't understand it.
This code is probably ludicrously old (.rhtml and start_form_tag put it at Rails 1 or 2) and really bad, it does not even run as there are two syntax errors as well as the quotes that look like an artifact from pasting the code into MS Word
# don't put a space before parens when calling methods in Ruby!
text_field (“cruncher”, “crunch”, {“size” => 30})
It would also give NoMethodError on #data = #cruncher.crunch.
In Rails 5 the same example can be written as:
class Cruncher
include ActiveModel::Model
attr_accessor :crunch
def crunch
# have no idea what this was supposed to do
end
end
class LookController < ApplicationController
def at
#cruncher = Cruncher.new(cruncher_params)
#data = #cruncher.crunch
end
private
def cruncher_params
params.fetch(:cruncher).permit(:crunch)
end
end
# I really have no idea what the actual routes are supposed to be
<%= form_for(#cruncher, url: '/look/at') do %>
<%= f.text_field(:crunch size: 30) %>
<% end %>
Its still just a strange and non RESTful example though. Sometimes garbage code is best left buried.
I do not understand what is the relationship between <%= text_field
(“cruncher”, “crunch”, {“size” => 30}) %> and the model. What do
text_fields attributes cruncher and crunch have to do with the model?
Nothing. There is no data binding. Its just a plain-jane text input.
But what about this #cruncher = Cruncher.new(#data_hash[:crunch]), why
do we now use #data_hash[:crunch]?
Because the author didn't know what they where doing. And probably had not figured out that you can pass hashes to your methods.

Related

Rails fields_for returning nil

Think I've solved this in the process of writing it. I'll post my solution below.
tl;dr Rails fields_for not generating the expected params.
I have a User class, with the following code:
class User < ActiveRecord::Base
has_one :woojit, dependent: :destroy, validate: true
accepts_nested_attributes_for :woojit
# ... more
end
and a Woojit class
class Woojit < ActiveRecord::Base
belongs_to :user
end
Our UsersController includes this method (we're using the gem Administrate, but it's a subclass of Rails' ApplicationController):
class WoojitsController < Admin::ApplicationController
def new
# ... other stuff
#woojit = #user.build_woojit
end
end
And our user _form.html.erb partial looks like this:
<%= form_for([namespace, page.resource], html: { class: "form" }) do |f| %>
<!-- User fields -->
<fieldset>
<legend>Woojit fields</legend>
<%= f.fields_for #woojit do |ff| %>
<%= render 'woojit_fields', f: ff %>
<% end %>
</fieldset>
<% end %>
So when I submit the form, what I'm hoping to see is a params hash with this subset:
{
"user" => {
"woojit_attributes" => {
"attr_1"=>"Foo", # etc
}
}
}
But what I'm actually getting in the create action is a params hash with this subset:
{
"user" => {
"woojit" => {
"attr_1"=>"Foo", # etc
}
}
}
Ie 'woojit' instead of 'woojit_attributes'. I could hack this in the create action, but that seems like a horrible way of resolving the problem. I want to know why the key is getting mis-named to begin with.
I originally tried the alternative of using the line <%= f.fields_for :woojit do |ff| %> (ie symbol instead of instance var), and not building a Woojit object in the #new action.
The #fields_for guide suggests in the One-To-One section that this should work, but the code in the block was never executed, so the line returned nil - and the woojit params never made it to the create action in any form.
So the problem was apparently that #fields_for behaves differently when given an instance of a model (#woojit), rather than a symbol (:woojit).
In the former instance it generates fields that reference (model name), eg:
<input type="text" name="user[woojit][woojit_attr]" id="user_pledge_pledgor_name" />
In the latter instance it generates fields fields that reference the model attributes, eg:
<input type="text" name="user[woojit_attributes][woojit_attr]" id="user_pledge_attributes_pledgor_name" />
Hope that saves someone some of the frustration I've just been through!

How do I properly use serialize to save arrays and hashes to database?

I'm obviously missing something fairly fundamental in my use of serialize. I'm trying to save several arrays and hashes to a database record, which I pull from various other records in my controller. I list them on a page to check them before saving, and have them posted in hidden fields to my create action. They are definitely hashes and arrays when I use them in my page view. However for some reason they get saved as escaped strings. Here's example code:
Model:
class Bulletin < ActiveRecord::Base
serialize :news
attr_accessible :news
end
View:
<%= form_for(#bulletin) do |f| %>
<%= f.hidden_field :news %>
<%= f.submit "Create bulletin", class: "btn btn-large btn-primary" %>
<% end %>
Controller:
...
def new
#bulletin = Bulletin.new
create_news
...
end
def create
#bulletin = Bulletin.new(params[:bulletin])
...
end
...
private
def create_news
...
if #bulletin.news == []
#bulletin.news = [["No news", "No news submitted this week."]]
end
end
I've literally included the bare minimum of code there for you to get it. There is more code in the create news function for instance which actually generates a nested array of news if it exists.
I should also note that I did have serialize :news, Array but it threw up an error "Attribute was supposed to be a Array, but was a String".
Edited to add -
Generated HTML (ie. input):
<input id="bulletin_news" name="bulletin[news]" type="hidden" value="[["Bulletin User", "A whole lot of news."], ["Bulletin User", "A whole lot of news."]]" />
These are pairs of values pulled from a test database, and joined into a nested array. There's a lot more than that, I cut it short for readability.
Output when querying the record from console:
"[[\"Bulletin User\", \"A whole lot of news.\"], [\"Bulletin User\", \"A whole lot of news.\"], [\"Another User\", \"A whole lot of news.\"], [\"Mr. Jovany Murazik\", \"A whole lot of news.\"]]"

What's the best way to do form field validations for phone numbers in Rails view without using a model?

I have the following form field in my index.html.erb view:
<%= form_tag("/calls/call", :method => "post") do %>
<br />
<%= text_field_tag(:call_to, "Number to call") %>
<br />
<%= text_field_tag(:call_from, "Number to call from") %>
<br />
<%= submit_tag("Dial") %></a>
<br />
<% end %>
I want to constrain the field to allow only 10 digit US phone numbers without using a model validation (as there is no model).
There's probably a lot of ways to do this, but what do you folks find to be the simplest to implement?
I've tried using the 'active_attr' gem, but didn't have much luck. With 'active_attr' I created a model called Call. Here's what the model looks like:
class Call
include ActiveAttr::Model
attribute :call_to
attribute :call_from
# attr_accessible :call_to, :call_from
validates_presence_of :call_to
validates_presence_of :call_from
end
My controller looks like this:
class CallsController < ApplicationController
def call
call_to = params["call_to"]
call_from = params["call_from"]
call_to.to_i
call_from.to_i
puts call_to
puts call_from
end
end
Am I supposed to instantiate an instance of a Call class in the controller or something along those lines?
#call = Call.new
Thanks in advance!
One client side option would be using the HTML5 type=tel input. Here's a link to some documentation. You will still want some server side validation though.

Rails - Add a method to a textfield

I'm trying to get the checkSwear method to run on each textfield before it's submitted..
I have basically this: (stripped down)
<%= form_for(#profile) do |f| %>
<div class="field">
<%= f.label 'I love to ' %>
<%= f.text_field :loveTo %>
</div>
<div class="field">
<%= f.label 'I hate to ' %>
<%= f.text_field :hateTo %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
In my controller I have:
def checkSwear
antiSwear.checkSwear(What goes here?)
end
In routes:
match '/check' => 'profiles#checkSwear'
Any help much appreciated!
(checkSwear is a separate gem; i.e. a separate problem! The what does here means what kind of variable is received from the form, to be put through the checkswear gem)
UPDATE:
Sorry for the camelcasing, I'm a Java developer studying Rails etc., old habits die hard. This is for a project. I'm supposed to be writing a small gem to do some ruby logic and apply it to something. The contents of the gem are:
module antiSwear
#swearwords = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#replacements = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
#swearwords.each do |swearword|
if text.include?(swearword)
index = #swearwords.index(swearword)
replacement = #replacements[index]
text.gsub(swearword, replacement)
end
end
return text
end
end
:/
This should really be done in model validations.
class Profile < ActiveRecord::Base
validate :deny_swearing
private
def deny_swearing
if AntiSwear.check_swear(love_to) || AntiSwear.check_swear(hate_to)
errors.add_to_base('Swearing is not allowed.')
end
end
end
That said, if you insist on this being in controller, you can check params[:profile][:love_to] and params[:profile][:hate_to] to see what's been submitted.
P.S. In this example I used proper ruby naming conventions, since we don't use "camelCasing".
Are you doing this as part of validation? You can do it one of a few ways. You can run the check before save, via a custom validation method or override the setter directly. I show you the custom validation approach here:
class Profile < ActiveRecord::Base
validate :clean_loveTo
protected
def clean_loveTo
errors.add(:loveTo, "can't contain swears") if antiSwear.checkSwear(loveTo)
end
end
I'm assuming checkSwear returns a boolean here.
I'd use an intersection on arrays, one of which is the source text split into words, then gsub the replacements in. You have to be sure to have a 1:1 relationship between the words and their replacements, in which case I'd suggest using a hash for your dictionary (coincidentally what hashes are sometimes called in other languages).
module antiSwear
# var names changed for formatting
#swears = ["f**k", "f***ing", "shit", "shitting", "lecturer"]
#cleans = ["fornicate", "copulating", "poop", "pooping", "Jonathan"]
def self.checkText(text)
# array intersection. "which elements do they have in common?"
bad = #swears & text.split # text.split = Array
# replace swear[n] with clean[n]
bad.each { |badword| text.gsub(/#{badword}/,#cleans[#swears.index(badword)] }
end
end
You might need to futz with text.split arguments if the replacement gets hung up on \n & \r stuff.

Edit a serialized hash in a form?

I'm serializing a hash that is stored in a settings field in a table, and would like to be able to edit that hash in a form field.
class Template < ActiveRecord::Base
serialize :settings
end
But I just do <%= f.text_area :settings %> then the text area just shows the serialized data instead of the hash.
How can I get the hash to show in the text area?
Maybe setting up another accessor for your model would work.
class Template < ActiveRecord::Base
serialize :settings
attr_accessor :settings_edit
before_save :handle_settings_edit, :if => lambda {|template| template.settings_edit.present? }
def settings_edit
read_attribute(:settings).inspect # should display your hash like you want
end
protected
def handle_settings_edit
# You may want to perform eval in your validations instead of in a
# before_save callback, so that you can show errors on your form.
begin
self.settings = eval(settings_edit)
rescue SyntaxError => e
self.settings = settings_edit
end
end
end
Then in your form use <%= f.text_area :settings_edit %>.
I have not tested any of this code, but in theory it should work. Good luck!
WARNING: Using eval like this is very dangerous, in this example a user could delete the entire Template table with one line in the edit box Template.destroy_all. Use a different method to convert the string to a hash if user input is involved.
... or you could use something like this (without any logic in model):
<% #template.settings.each do |name, value| %>
<div>
<%= label_tag name %>
<%= text_field_tag "template[settings][#{name}]", value %>
</div>
<% end %>
you should use something like
class Template < ActiveRecord::Base
serialize :settings, Hash
end

Resources