Rails 3: adding a yes/no "recommended" option to user posts - ruby-on-rails

I'm new to rails and I'm working on a simple app where users create posts with content, hehehe. But since I'm real new I'm having some confusion. When users create a post I want them to have a 'recommended option' yes/no which defaults on the no. So if a user wants to recommend a post he simply selects the yes radio button before he submits the form. I already have the user and post model working to create a post with a title and body. The model relationship is users has_many posts, and posts belongs_to user.
I'd like to keep it really simple and just add a 'recommended' attribute to the post model, using no/yes radio buttons which default to no. I'm confused about the rails form helpers and how to add a yes/no attribute to my post migration. Then how would I select an array of the posts which are recommended by a specific #user?
Thanks a lot!

in the migration:
def self.up
add_column :posts, :is_recommended, :boolean, :default => false
add_column :posts, :message, :text
end
posts_controller.rb:
#rails 2 way:
#recommended_posts = Post.find(:all, :conditions => {:is_recommended => true, :user_id => params[:user_id]})
#rails 3 way:
#recommended_posts = Post.where(:is_recommended => true, :user_id => params[:user_id])
views/posts/new.html.erb: (using check_box rather than radio_button)
<% form_for(#post) do |f| %>
<p>
<%= f.label :message %><br />
<%= f.text_area :message %>
</p>
<p>
<%= f.label 'Recommend' %><br />
<%= f.check_box :is_recommended %>
</p>
<% end %>

Related

Store value from another table form in rails

I have a users table and games table. Games table has user_id. The help I want is how can I change/enter the value of city of birth from the game's form which is a field in the user table. I am using the try() method to display the value of city of birth from the user table in the game's form.
user.rb
has_many :games, dependent: :destroy
game.rb
belongs_to :user
_form.html.erb(game)
<div class="custom-hidden field">
<%= form.label :user_id %>
<%= form.number_field :user_id %>
</div>
<div class="field">
<%= form.label :city_of_birth, "User City of Birth" %>
<%= form.text_field :city_of_birth, :value => #user.try(:city_of_birth) %>
</div>
<div class="field">
<%= form.label :game_name %>
<%= form.text_field :game_name %>
</div>
I'm going to answer this question assuming that this form is meant to create a Game, which belongs_to :user, since there is a user_id in the form, and that user.city_of_birth is a string.
The traditional way to do this would be to use theaccepts_nested_attributes_for feature of Rails.
https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
However, I would strongly suggest that you consider writing a form object to handle this, so that the responsibility for validating this mixed-model form is held cleanly in one place. I suggest using a gem like Reform to make this process easier.
# app/forms/games/new_game_form.rb
class Games::NewGameForm < Reform::Form
property :user_id, validates: { presence: true }
property :city_of_birth, virtual: true, validates: { presence: true }
property :game_name, validates: { presence: true }
def save
Game.transaction do
user.update!(city_of_birth: city_of_birth)
Game.create(user: user, game_name: game_name)
end
def user
#user ||= User.find(user_id)
end
end
This form object can then be used in place of the Game instance.
# GamesController
def new
#form = Games::NewGameForm.new(Game.new)
end
<!-- new.html.erb -->
<%= form_with #form, url: games_path, method: :post do |f| %>
<!-- your form fields -->
<% end %>
Please note that it appears very odd that you're accepting a user_id in the form, but also reading the city_of_birth from #user. If the #user for whom the game is being created is already known (perhaps the signed in user), then it's useless (and a security risk) to accept the ID in the form - you can simply use the same method that was used to set #user.

How to update child record in nested form in rails

How would I update a collection of child 'Product' rows when submitting the form below.
Many thanks
class User < ActiveRecord::Base
has_many :products, :class_name => 'Product', :inverse_of => :user
accepts_nested_attributes_for :products
end
class Product < ActiveRecord::Base
belongs_to :user, :inverse_of => :products
end
The View
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<%= f.fields_for :products do |builder| %>
<% builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Here is somewhat minimal update action:
# app/controller/users_controller.rb
def update
# Get the user in param
#user = User.find(params[:id])
# Update attributes
if #user.update_attribute(params[:user])
# Redirect to :show for this user.
redirect_to :show
else
# update_attriubtes failed. Render the edit view.
render :edit
end
end
Secondly, you also need to ensure the attributes can be mass assigned such that the call to #user.update_attributes succeeds. Since you've not tagged your question with the appropriate Rails version, following is what you can do for Rails 3 and Rails 4:
Rails 3 - Define each attribute you want to mass assign as attr_accessible
Rails 4 - Permit parameters in controllers params.require(:user).permit(...).
Note that in your view you are not outputting the result of the helper text_field on this line:
<% builder.text_field :name %>
You need to use <%=...%> (note the equals = sign) in order to output the result of expressions within <%=...%>. This might have just been a typo here as you've done this correctly on all other elements. Replace the line with:
<%= builder.text_field :name %>
Also, suggest reading the "Action Controller Overview" of guides for details on Controllers.

Ruby on Rails: Association model, do I need controller to save data?

I'm new to Ruby on Rails, maybe someone can help me.
I have an association, I was wondering if I need a controller to save data into the database table?
I have user.rb model
has_many :businesses
I have business.rb model
belongs_to :user
I have this in the business migration file
class CreateBusinesses < ActiveRecord::Migration
def change
create_table :businesses do |t|
t.integer :user_id
t.string :name
t.string :street
t.string :state
t.string :city
t.integer :zip
t.timestamps
end
end
end
I'm wondering if I need to create a controller file to save data into business table?
I have something like this in views/users/profile.html.erb page
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :company_name %>
<%= f.text_field :company_name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
How do I set this form so that I can save my :company_name into business's table :name as well, and so I can also add :street, :state, etc... to this form?
I only generated a model, and there is no controller for businesses yet.
Thanks!
You don't necessarily need a business controller, but you will need a user controller. You can have your user controller save associated objects for your user by way of nested attributes.
Firstly, allow you user model to accept nested attributes for the business relation:
user.rb
accepts_nested_attributes_for :business
then add fields_for the business object into your user form:
<%= form_for(#user) do |f| %>
<div class="field">
<%= f.label :company_name %>
<%= f.text_field :company_name %>
</div>
<%= fields_for :business do |fields| %>
<%= fields.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
The business attributes will come through as part of the params user => {:name => 'Jim', :business_attributes => {:name => 'Jims business'}}
You can then pass these straight into the update of your user object in the create or update calls of your user controller:
def update
#user = User.find(params[:id])
#user.update_attributes(params)
end
And the business params will be handled by the accepts_nested_attributes functionality!
The example above explains a single instance example, since you have a has_many relation you will need to take the above as a starting point and learn how to adapt it to allow many child items. Below are some resources to help you learn this, rather than giving you the entire code and you not learn anything!
Read more about nested attributes
A handy railscast to follow for multiple child instances, with javascript controls
Of course you need a controller. Not necessarily the same controller, but one is needed.
The controller is needed to connect the view and the model. Without it when you submit your data there is no action to send it. Obviously, the database won't be modified this way. You can't even display your view without an action in the controller.
Models without corresponding containers are only used when it is closely attached some other model, like a forum-comment pair. So you can let the user controller to handle business data, but that is not really recommended.

Nested attributes unpermitted parameters

I have a Bill object, which has many Due objects. The Due object also belongs to a Person. I want a form that can create the Bill and its children Dues all in one page. I am trying to create a form using nested attributes, similar to ones in this Railscast.
Relevant code is listed below:
due.rb
class Due < ActiveRecord::Base
belongs_to :person
belongs_to :bill
end
bill.rb
class Bill < ActiveRecord::Base
has_many :dues, :dependent => :destroy
accepts_nested_attributes_for :dues, :allow_destroy => true
end
bills_controller.rb
# GET /bills/new
def new
#bill = Bill.new
3.times { #bill.dues.build }
end
bills/_form.html.erb
<%= form_for(#bill) do |f| %>
<div class="field">
<%= f.label :company %><br />
<%= f.text_field :company %>
</div>
<div class="field">
<%= f.label :month %><br />
<%= f.text_field :month %>
</div>
<div class="field">
<%= f.label :year %><br />
<%= f.number_field :year %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<%= f.fields_for :dues do |builder| %>
<%= render 'due_fields', :f => builder %>
<% end %>
<% end %>
bills/_due_fields.html.erb
<div>
<%= f.label :amount, "Amount" %>
<%= f.text_field :amount %>
<br>
<%= f.label :person_id, "Renter" %>
<%= f.text_field :person_id %>
</div>
UPDATE to bills_controller.rb
This works!
def bill_params
params
.require(:bill)
.permit(:company, :month, :year, dues_attributes: [:amount, :person_id])
end
The proper fields are rendered on the page (albeit without a dropdown for Person yet) and submit is successful. However, none of the children dues are saved to the database, and an error is thrown in the server log:
Unpermitted parameters: dues_attributes
Just before the error, the log displays this:
Started POST "/bills" for 127.0.0.1 at 2013-04-10 00:16:37 -0700
Processing by BillsController#create as HTML<br>
Parameters: {"utf8"=>"✓",
"authenticity_token"=>"ipxBOLOjx68fwvfmsMG3FecV/q/hPqUHsluBCPN2BeU=",
"bill"=>{"company"=>"Comcast", "month"=>"April ",
"year"=>"2013", "dues_attributes"=>{
"0"=>{"amount"=>"30", "person_id"=>"1"},
"1"=>{"amount"=>"30", "person_id"=>"2"},
"2"=>{"amount"=>"30", "person_id"=>"3"}}}, "commit"=>"Create Bill"}
Has there been some change in Rails 4?
Seems there is a change in handling of attribute protection and now you must whitelist params in the controller (instead of attr_accessible in the model) because the former optional gem strong_parameters became part of the Rails Core.
This should look something like this:
class PeopleController < ActionController::Base
def create
Person.create(person_params)
end
private
def person_params
params.require(:person).permit(:name, :age)
end
end
So params.require(:model).permit(:fields) would be used
and for nested attributes something like
params.require(:person).permit(:name, :age, pets_attributes: [:id, :name, :category])
Some more details can be found in the Ruby edge API docs and strong_parameters on github or here
From the docs
To whitelist an entire hash of parameters, the permit! method can be used
params.require(:log_entry).permit!
Nested attributes are in the form of a hash. In my app, I have a Question.rb model accept nested attributes for an Answer.rb model (where the user creates answer choices for a question he creates). In the questions_controller, I do this
def question_params
params.require(:question).permit!
end
Everything in the question hash is permitted, including the nested answer attributes. This also works if the nested attributes are in the form of an array.
Having said that, I wonder if there's a security concern with this approach because it basically permits anything that's inside the hash without specifying exactly what it is, which seems contrary to the purpose of strong parameters.
or you can simply use
def question_params
params.require(:question).permit(team_ids: [])
end
Actually there is a way to just white-list all nested parameters.
params.require(:widget).permit(:name, :description).tap do |whitelisted|
whitelisted[:position] = params[:widget][:position]
whitelisted[:properties] = params[:widget][:properties]
end
This method has advantage over other solutions. It allows to permit deep-nested parameters.
While other solutions like:
params.require(:person).permit(:name, :age, pets_attributes: [:id, :name, :category])
Don't.
Source:
https://github.com/rails/rails/issues/9454#issuecomment-14167664
Today I came across this same issue, whilst working on rails 4, I was able to get it working by structuring my fields_for as:
<%= f.select :tag_ids, Tag.all.collect {|t| [t.name, t.id]}, {}, :multiple => true %>
Then in my controller I have my strong params as:
private
def post_params
params.require(:post).permit(:id, :title, :content, :publish, tag_ids: [])
end
All works!
If you use a JSONB field, you must convert it to JSON with .to_json (ROR)

Nested model form not submitting everything

I heard about this community while listening to Hypercritical and I am excited to join in with my first question. I am working on my first rails App and I have run into an issue that I cannot seem to crack. I have been watching Railscast, Lynda.com, and Googling for days but I still cannot comprehend how to create a form that that will update my has_many :through associations at once. Allow me to try an explain what I am doing.
My Goal:
The firm I work for provides many "Service Offerings" and I want to be able to create a new service offering on one page and have it create the contacts and other information that is associated with it. The additional information such as "contacts" will live in their own tables because they may need to be referenced by many "Service Offerings."
Problem:
When I submit the form the "Service Offering" fields submit and are entered into the database, but the fields for the "Business Developer" do not. Obviously, I would like everything to be entered into its appropriate table and the for the IDs to be linked in the join table. I would really appreciate any insight that you could provide.
What I Have So Far: What you see below is Service Offerings and Business Developers. Eventually I will be adding Contacts, Photos, and Files but I thought I would start simply and work my way up.
Models:
class ServiceOffering < ActiveRecord::Base
attr_accessible :name, :description
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developer_service_offerings
end
class BusinessDeveloper < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :business_developer_service_offerings
has_many :service_offerings, :through => :business_developer_service_offerings
end
class BusinessDeveloperServiceOffering < ActiveRecord::Base
belongs_to :business_developer
belongs_to :service_offering
end
Controller:
def new
#service_offering = ServiceOffering.new
#service_offering.business_developers.build
end
def create
#service_offering = ServiceOffering.new(params[:service_offering])
if #service_offering.save
redirect_to(:action => 'list')
else
render('new')
end
end
View:
<%= form_for((#service_offering), :url => {:action => 'create'}) do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description%>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developer do |builder| %>
<p>
<%= builder.label :first_name%>
<%= builder.text_field :first_name %>
<%= builder.label :last_name%>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
I figured it out. It turns out a few things were wrong and needed to be changed in addition to the two suggestions #Delba made.
The Form:
I took a look at RailsCasts #196 again and noticed that my form looked different than the one used there, so I tried to match it up:
<%= form_for #service_offering do |f|%>
<p>
<%= f.label :name%>
<%= f.text_field :name %>
<%= f.label :description %>
<%= f.text_field :description %>
</p>
<%= f.fields_for :business_developers do |builder| %>
<p>
<%= builder.label :first_name %>
<%= builder.text_field :first_name %>
<%= builder.label :last_name %>
<%= builder.text_field :last_name %>
</p>
<%end%>
<%= f.submit "Submit" %>
<%end%>
Initially, this presented an error:
undefined method `service_offerings_path'
Routes:
This lead me to learn about RESTful Routes because I was using the old routing style:
match ':controller(/:action(/:id(.:format)))'
So I updated my routes to the new RESTful Routes style:
get "service_offerings/list"
resource :service_offerings
resource :business_developers
attr_accessible:
That got the form visible but it was still not working. So I did some searching around on this site and found this post that talked about adding "something_attributes" to your parent objects model under attr_accessible. So I did:
class ServiceOffering < ActiveRecord::Base
has_many :business_developer_service_offerings
has_many :business_developers, :through => :business_developer_service_offerings
accepts_nested_attributes_for :business_developers
attr_accessible :name, :description, :business_developers_attributes
end
That change along with #Delba's suggestion shown in the above model and controller listed below solved it.
def new
#service_offering = ServiceOffering.new
#business_developer = #service_offering.business_developers.build
end
You just forgot to assign #business_developper.
def new
#service_offering = ServiceOffering.new
#business_developper = #service_offering.business_developpers.build
end
-
#business_developer = #service_offering.business_developers.build
initializes an instance of biz_dev which is then available in the view.
fields_for :biz_dev isn't really tied to this instance but to the many-to-many relationship btw serv_off and biz_dev.
In this way, you can add multiple input for additional biz_dev if you initialize another biz_dev instance in your controller. For instance:
5.times { #service_offering.biz_dev.build }
will add additional fields in your form without you having to declare them in your view.
I hope it helped.

Resources