Am I doing symbols right? - ruby-on-rails

I understand that symbols and strings are not same, but I don't understand the concept of symbols.
As I understand, a symbol refers to something else. I use the word refer here for my better understanding, not in the in sense of C/C++ pointers or references.
When I create a new controller in Rails, using something like rails g controller Posts, does Ruby automatically create a new symbol :posts, which refers to the Posts controller? Is this :posts symbol globally accessed from anywhere in the application? Only in that case, I can understand a construct like:
resources :posts
which Ruby can translate for itself: "Ok, I know that :posts refers to posts_controller, so I need to create CRUD routes for that controller". Am I right?
What roles do symbols :title and :content have in this example? I assume that form.label is equivalent to form.label(:title).
<%= form_for #post, :html => {:class => "new_post"} do |form| %>
<%= form.label :title %><br>
<%= form.text_field :title %><br>
<%= form.label :content %><br>
<%= form.text_area :content %><br>
<%= form.submit("Add new post") %>
Are there built-in symbols like :all and symbols that are created on-the-way? When I say on-the-way, as I can see when I create some method like:
def something
...
end
Does Ruby create a new symbol :something, which will refer to method something? If that is correct, why do I need that symbol, where can I can use it?
#posts = Post.all #Or Post.all() => Standard OOP approach, i know what happens here
#posts1 = Post.find(:all) # Dont know what happens here
What about #posts1, what am I doing here? What do :all mean here? Is it some constant value (not string) for Ruby to know that it must pull every Post from DB?

A Symbol in Ruby is just an immutable string with a special syntax. It's used mostly so you can use less memory when doing string matching like operations as in:
options = { :me => 'joe' }
puts options[:me]
This creates a single symbol for :me, if you used strings:
options = { 'me' => 'joe' }
puts options['me']
Two strings would have been created, since strings in Ruby are mutable. So, no, there's no special meaning and it's not something that refer on something other, it's mostly a hack to the fact that strings in Ruby are mutable by default.
As for your last question, :all is just a value that the method implements in something like "a == :all ? return everything : so something else". It's just a method parameter.

Symbol is just an immutable interned string meaning that all symbol variables with the same value point to exactly the same memory location. They also never garbage collected so avoid creating them with to_sym. They are frequently used as keys in hash tables but here's the catch - some hash tables you'll receive from external libraries (e.g. API clients) have strings as keys so when you try to get the value as you're used with with obj[:some_attribute] you'll get nil.

:all is instance of Symbol class, it's built-in Ruby class. All
#posts1 = Post.find(:all)
line is deprecated way to load all posts to #posts1 variable. :all symbol is what indicates that you want to fetch all posts. If you type:
#post = Post.find(5)
you'd get only one post, the one with id = 5.

symbol is the same as the string, but one with the same value have the same memory address. And this is a key difference.
puts 'string'.object_id
puts 'string'.object_id
puts 'string'.object_id
#=> 3099310
#=> 3099310
#=> 3099310
puts :symbol.object_id
puts :symbol.object_id
puts :symbol.object_id
#=> 3098410
#=> 3021341
#=> 3012440
next
Post.find :all
Same as
Post.find 'all'
:posts isn't reference to posts_controller
resources :posts
What does it mean? resources is a method who generate standart CRUD routes. For search controller, it use the naming convention. If resource named as :posts, then routes will be set to PostsController.

I will recommend you to have a look to this blog Ruby Symbol and String.
You will get a lot more apart from symbol and string definition with details example.

Related

Rails: can't correctly invoke nested routes

I have the following routes:
shallow do
resources :countries do
resources :airports
end
end
I'm having trouble with invoking two of the routes.
The airports_controller.rb file begins
def create
Rails::logger.debug "!!! Building airport with parameters #{params}"
#country = Country.find(params[:country_id])
Rails::logger.debug "!!! Found airport #{#country.name}"
#airport = #country.airports.build(params[:airport])
The last line gives the error Minitest::UnexpectedError: ActiveModel::ForbiddenAttributesError: ActiveModel::ForbiddenAttributesError, but the params I'm calling with are
!!! Building airport with parameters {"airport"=>{"iata_code"=>"CCC",
"icao_code"=>"CCCC", "name"=>"Airport 3", "city"=>"City 3", "latitude"=>"1.5",
"longitude"=>"1.5"}, "country_id"=>"980190962", "controller"=>"airports",
"action"=>"create"}
and as far as I can see all of those are in my permitted parameters:
def airport_params
params.require(:airport).permit(:iata_code, :icao_code, :name, :city, :latitude, :longitude, :notes, :country_id)
end
Secondly, my airports\_form.html.erb begins <%= form_for [#country, #airport] do |f| %>, but that gives an error Minitest::UnexpectedError: ActionView::Template::Error: undefined method 'airports_path' for #<#<Class:0x4b0ba30>:0x55a2e48>. Yes, that path is undefined, but I was trying to get to path country_airports_path, which is defined.
So what am I missing here?
In "def create"
def create
...
#airport = #country.airports.build(airport_params)
You've correctly created the "airport_params" function, however you're not calling it. That should explain the ForbiddenAttributesError.
As for the undefined method, at first glance it looks like an incorrect association, are your models correctly related (i.e. belongs_to and has_many)? If so, you could try adding url: country_airports_path to the form_for field.
Attributes
The attributes error you've received is basically caused by your non-use of strong_params.
Rails 4 introduced strong_params to give you the ability to define specific attributes to pass to your model (prevents mass assignment). The problem you have is that you're using the Rails 3 way - passing all of the attributes without whitelisting them
As pointed out, you'll be better doing this:
#app/controllers/airports_controller.rb
Class AirportsController < ApplicationController
def create
#airport = Airport.new(airport_params)
end
private
def airport_params
params.permit(:airport).permit(:your, :params).merge(country_id: params[:country_id])
end
end
However, I believe there's something deeper you need to consider.
You're currently using .build for your airport model object. Whilst there's nothing wrong with this, you need to consider what you're trying to achieve...
The Airport model object is a standalone object. You can easily associate it with your Country model by setting the foreign_key in your strong params (demonstrated above)
If you use this line: #airport = #country.airports.build, you're working with the Country object, which IMO will open yourself up to errors down the line. As mentioned, there's nothing "wrong" with what you're doing; it's just that I'd either work with the Airport model directly (as written above), or use accepts_nested_attributes_for to work with the Country model
--
Route
Secondly, your path error is going to be caused by your form_for
form_for builds a form from an ActiveRecord object - which it does by taking arguments such as model_name to build the "url" / "action" for the form.
This means every time you populate a form_for object with an ActiveRecord object (variable), Rails will build a route based off of what you pass it (it does not know whether the object is nested or not)
<%= form_for #airport do |f| %>
# builds route using "airport_path"
<% end %>
If you want to create a nested form, you'll be much better using an array of ActiveRecord objects, as to provide Rails with the knowledge you're using a nested resource:
<%= form_for [#country, #airport] do |f| %>
# builds route using "country_airport_path"
<% end %>

ruby on rails search form

I'm new to RoR and I've managed to make a basic search form but keep getting errors when trying to expand the search tags (name).. I have a model with various data (location, website, email, telephone) and was wondering how I can add these to my current search code.
/models/ciir.rb
def self.search(search)
if search
find(:all, :conditions => ['name LIKE ?', "%#{search}%"])
else
find(:all)
end
end
static_pages_controller.rb
def home
#ciirs = Ciir.search(params[:search])
end
/home.html.erb
<%= form_tag ciirs_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search] %>
<%= submit_tag " Search Database Records ", :name => nil %>
</p>
<% end %>
When clicking the submit button (no search terms) the url is:
ciirs?utf8=✓&search=
but when modifying the name condition to something like 'website' the url changes to
ciirs?utf8=✓&search=&commit=+Search+Database+Records+ –
Since you mentioned you are new to RoR, I must share the way I learned RoR was reading, using and analyzing one issue at a time. I would suggest you to take a look at following points one at a time and try & learn how RoR treats them and how these fit your question:
How form_tag works?
How text_field_tag works?
Once you have understood form_tag, difference between text_field_tag and f.text_field?
How params objects are created, and it uses names of form controls?
How and when to use GET and/or POST form methods? Inadvertently, what are different types of method and when to use them?
How URL are used in the form_tag and what components are they made of?
Sprinkle a bit of knowledge of Ruby language by learning between Arrays and Hashes? In fact, learn Ruby as much as you can.
Answering your question,
/home.html.erb
<%= form_tag "/static_pages/home", :method => 'post' do %>
<p>
<%= text_field_tag "search[name]", params.has_key?("search") && params[:search].has_key?("name") ? params[:search][:name] : "" %>
<%= submit_tag " Search Database Records " %>
</p>
<% end %>
/models/ciir.rb
def self.search(search)
if search
find(:all, :conditions => ["name LIKE '%?%'", search[:name]])
else
find(:all)
end
end
So I modified your form, and told RoR about search params containing data for name.
params is a Hash (which is a key-value pair) having key named search, which further is a Hash having key named name.
The same principle is followed in the model code. We passed the Hash of key search to the function and in there, used the value of key named name.
I also updated the url in form_tag, to point it to home action of your controller. Assuming that you have added it to your routes.rb file, it usually follows the pattern controller_name/action_name or the function name action_name_controller_name_path or action_name_controller_name_url. Run rake routes command at your root directory to list out all paths in your application.
Also note, I used POST method instead of original GET. You may wish to use GET here, so please change it back.
I hope this works.
I found no error in your code. the url changed to ciirs?utf8=✓&search=&commit=+Search+Database+Records+ is normal. submit_tag generates a button named "commit" defaultly, it will be parsed in the params. I see you add :name => nil , it will fix the problem, the other part of your code needn't to be modified. I copied your code and tested it, it ran smoothly.

Ruby on Rails Passing Parameter In URL

This is probably a very simple fix but I've been unable to find an answer just yet.
My application has orders and tasks. Orders have many tasks. When a user clicks new task in the show order view, it passes the order.id:
<%= link_to "New Task", new_task_path(:order_id=> #order.id) %>
The url shows:
/tasks/new?order_id=1
I just don't know how to extract this and use it in my form? I have tried:
<%= f.text_field :order_id, :value => #order_id %>
But it's not working.
You can do:
<%= f.text_field :order_id, :value => params[:order_id] %>
Alternately, capture the value (with params) in the controller and assign it to #order_id there.
You are doing this wrong, which is a big deal in Rails where convention-over-configuration is such an important ideal.
If an order has many tasks, your route should look like this:
/orders/:order_id/tasks/new
And your routes should be configured thusly:
resources :orders do
resources :tasks
end
You should [almost] never find yourself passing around record ids in the query string. In fact, you should almost never find yourself using query strings at all in Rails.

Formtastic checkboxes not getting checked when editing resource, when using MongoMapper

With the following Store and Service models, managed with MongoMapper:
class Store
include MongoMapper::Document
key :service_ids, Array, :typecast => 'ObjectId'
many :services, :in => :service_ids
end
class Service
include MongoMapper::Document
key :name, String
many :stores, :foreign_key => :service_ids
end
I have this form, done with Formtastic:
<%= semantic_form_for #store, :url => admin_store_path(#store), :method => :put do |form| %>
<%= form.input :service_ids, :label => "Select Store Services",
:as => :check_boxes,
:collection => Service.all %>
<% end -%>
The controller uses Inherited Resources, and the edit action is implicit.
When editing a #store with services already associated with it, the checkboxes for the latter don't show as checked.
Formtastic's README warns it doesn't support MongoMapper officially, but it also says people have been using both together successfully, and I've seen some examples of this online.
I suspect Inherited Resources also doesn't support it, from what I've seen from Devise + Simple Form, both from the same authors and which don't support MM. They're working towards using an ORM adapter in their gems, but it isn't ready yet AFAIK.
And I've had problems with it already, I'm overriding the update action to get it to work:
def update
store = Store.find(params[:id])
if store.update_attributes!(params[:store])
flash[:notice] = 'Store was successfully updated.'
redirect_to admin_store_path(store)
else
redirect_to new_store_path
end
end
Does anybody know where the conflict with MM is, either in Formtastic or IR, and a hack just to get these checkboxes checking?
Most likely a Formtastic issue. It looks like the problem is here: https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/check_boxes_input.rb#L122
Formtastic calls #store.service_ids to find the selected boxes. Service_ids returns an array of ObjectId's, but Formtastic was expecting an array of Store objects. If we follow Formtastic's code we'll see it tries a couple methods to find out how to get the "value" out of those ObjectId's and will eventually settle on "to_s" (see https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/form_builder.rb#L20). Unfortunately, the to_s of an ObjectId is not the same as the id of your Store objects.
A hack that might make it work is to add an "id" method to ObjectId that returns self (Formtastic looks for id before it looks for to_s). A more appropriate patch would be to override this method https://github.com/justinfrench/formtastic/blob/master/lib/formtastic/inputs/base.rb#L104 to properly introspect MongoMapper associations, so that you could write form.input :services and it would turn that into an input with name of "service_ids" while still using the services method of your object. With that change it would still properly call #store.services and find the same kind of objects as Store.all and just work.
If you want to go that route, Store.associations[:services] should get you MongoMapper's definition of the association which you can introspect (see https://github.com/jnunemaker/mongomapper/blob/master/lib/mongo_mapper/plugins/associations/base.rb) but note that associations have been refactored a bit since the 0.8.6 gem, they're now in the separate classes BelongsToAssociation, OneAssociation, and ManyAssociation that each inherit from Associations::Base.
So, it doesn't seem like there's a simple fix. The other option is to generate your checkboxes by hand.
(Aside: I'm a little confused by your update method because I'd expect IR to do exactly what you've written internally, but if you had to write it that way to get it to work, so it is...)

Rails - default value in text_field but only for new_record?

On a Content model have an attribute named slug. When creating a new record, I want to use a helper to populate this field, but on an existing record I want to use the value from the database.
Currently I have:
<% if #content.new_record? %>
<%= f.text_field :slug, :value => "#{generate_slug(6)}" %>
<% else %>
<%= f.text_field :slug %>
<% end %>
But that seems a bit verbose. Is this the best way, or is there no other way? (Rails newb just trying to find the "Rails way" on issues I'm unsure of)
Edit
I should note that the helper is currently in /app/helpers/application_helper.rb Moved to be a private action in the Contents controller. David's answer worked great.
In your controller
#content.slug ||= generate_slug(6)
This will assign a value to the slug attribute if none is present
Then, in your view you can simply use
<%= f.text_field :slug %>
Options
Try after_initialize callback in your model.
Try creating a method in your model where you set defaults and call it in your new action in the controller. Also call this method if your create fails and you render new. Remember to set default only when no value exists by using the ||= operator.
Example to follow. I'm typing on phone!
I happen to use jQuery in my projects, so when I want some functionality like this, I usually use something like labelify. Then, I'd use something like <%= f.text_field :slug, :title => generate_slug(6) %>. (Hot tip, you don't need to put the #generate_slug call inside of a string if it returns something that will resolve to a string by itself, in fact it's more performant if you don't.)
If you don't want to go with jQuery approach, you might want to wrap this piece of logic in your model.
def Content < ActiveRecord::Base
def slug
self.new_record? ? self.slug_for_new_record : attributes[:slug]
end
private
def slug_for_new_record
# I don't know what you're doing in generate_slug, but it sounds model-
# related, so if so, put it here and not in a helper
end
end
If it really belongs in the view, still another option is to just make your Ruby a little bit more concise (you'll have to judge if this is more readable):
<%= f.text_field :slug, :value => (generate_slug(6) if #content.new_record?) %>
Don't forget the parens surrounding (generate_slug(6) if #content.new_record?). If you do, the if will be applied to the text_field, which is not what you want.
But there are still more ways to do it. The above line of code isn't great if your logic might change and you're pasting this code all over your rails project. When I wanted to add a 'required' class to my text fields but only if they were a new record (we had some legacy data that we didn't want to make people clean up), I created my own form builder with a required_field method that just called text_field and added a 'required' class if the item was a new record. This might seem like a work, but we have around 20 different forms, each with potentially multiple required fields, and it's a lot easier to change the business logic in one place. So if you really think this logic belongs in the view but you've got a ton of these lines of code and you don't want to have to change it in a million places, then FormBuilder is the way to go. I think this is in most cases prettier and more appropriate than a helper, but again, beauty is in the eye of the beholder. Here's my code somewhat adapted for your case:
# config/environment.rb
ActionView::Base.default_form_builder = NamespacesAreFun::FormBuilder
# lib/namespaces_are_fun/form_builder.rb
module NamespacesAreFun
class FormBuilder < ActionView::Helpers::FormBuilder
def slug_field(method, options = {})
opts = options.to_options
opts.merge!(:value => generate_slug) if self.object.new_record?
text_field(method, opts)
end
end
end
# views/.../your_view.html.erb
<%= f.slug_field :slug %>
Hopefully in all of these different approaches is one that fits your project.

Resources