Multiple (n) identical nested forms generated square-times(n*n) when validation fails - ruby-on-rails-3

User has two addresses shipping(:address_type=0) and billing(:address_type=1)
User form with 2 classic nested forms for each address type are generated square times every submit and failed validation.
Models:
class User < ActiveRecord::Base
has_many :addresses, :dependent => :destroy
accepts_nested_attributes_for :addresses
validates_associated :addresses
end
class Address < ActiveRecord::Base
belongs_to :user
validates :user, :address_type, :first_name, :last_name, :street
end
Controller
class UsersController < ApplicationController
public
def new
#user = User.new
#shipping_address = #user.addresses.build({:address_type => 0})
#billing_address = #user.addresses.build({:address_type => 1})
end
def create
#user = User.new(params[:user])
if #user.save
#fine
else
render => :new
end
end
Uncomplete Form
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses, #shipping_address do |f|
=f.hidden_field :address_type, :value => 0
=ff.fields_for :addresses, #billing_address do |f|
=f.hidden_field :address_type, :value => 1
=ff.submit

The form should look like this:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for :addresses do |f|
Nothing else.
Addressess is already a collection, so you should have just one rendering of it.
Also that ":addresses, #shipping_address" makes it to render addresses AND shipping address, even if it's included in #user.addresses.
The addressess built in new action will show there because they are in the addresses collection.
EDIT:
If you need only these two addresses, you can sort it and pass it to fields_for directly:
=form_for #user, :html => { :multipart => true } do |ff|
=ff.fields_for ff.object.addresses.sort{|a,b| a.address_type <=> b.address_type } do |f|
That should do it.

Surprised? I guess not but I was. I found it am I correct? And its stupid and simple.
There is no #shipping_address nor #billing_address when validation fails and rendering the new action (the form) again. But #user has already 2 addresses builded and nested form behave correctly to render each twice for first time failed validation.
def create
#user = User.new(params[:user])
if #user.save
#fine
else
#user.addresses.clear
#user_address = #user.addresses.build({:address_type => 0})
#user_address.attributes = params[:user][:addresses_attributes]["0"]
#billing_address = #user.addresses.build({:address_type => 1})
#billing_address.attributes = params[:user][:addresses_attributes]["1"]
render => :new
end
end

Related

Google Maps for Rails info window example

Maybe it's a case of the Mondays but I'm having a really difficult time with infowindows and the Google Maps for Rails gem. Does anyone know of a tutorial or example?
All I want to do is set up a default infowindow to open when you click on a marker. I've gathered that I need to make a partial and set the options in the map but I just can't seem to bring it all together.
Thanks!
Nevermind, it finally clicked. Here's my code for a basic example, hopefully it will help someone else in the future.
location model
class Location < ActiveRecord::Base
include Rails.application.routes.url_helpers
default_scope order('locations.id ASC')
acts_as_gmappable
attr_accessible :name,
:address,
:city,
:province,
:postal_code,
:country,
:phone,
:ext,
:phone_alt,
:ext_alt,
:latitude,
:longitude
geocoded_by :address
validates_presence_of :name
validates_presence_of :address
validates_presence_of :city
validates_presence_of :province
validates_presence_of :postal_code
validates_presence_of :country
after_validation :geocode, :if => :address_changed?
def gmaps4rails_address
#describe how to retrieve the address from your model, if you use directly a db column, you can dry your code, see wiki
"#{self.address}, #{self.city}, #{self.country}"
end
end
location controller
class LocationsController < ApplicationController
def show
#location = Location.find(params[:id])
#json = #location.to_gmaps4rails do |location, marker|
marker.infowindow render_to_string(:partial => "/layouts/partials/infowindow", :locals => { :location => location})
end
respond_to do |format|
format.html
end
end
end
infowindow partial (haml)
.location-data{id: location.id}
.location-name
= location.name.capitalize
.location-address
= location.address.capitalize
.location-city= location.city.capitalize
.location-province
= location.province.capitalize
.location-postal-code
= location.postal_code
.location-country
= location.country
.location-phone
= location.phone
.location-extension
= location.ext
.location-alt-phone
= location.phone_alt
.location-alt-phone-extension
= location.ext_alt
show view (haml)
#map-column
%h1
Find a retailer near you
= gmaps("markers" => {"data" => #json, "options" => {"link_container" => "map_link_" } })

Limiting how often a user can post on a particular person's profile/wall in Rails

How can I limit a user to only to being able to post once or twice per day on a particular users's wall? I primarily want to do it in order to limit spam. My code for the wall, models, view, and controllers are below. I don't really know how to go about it as I'm new to rails but I know there is something time.now. I'm not exactly sure how to implement such a feature.
Class UsersController < ApplicationController
def show
#user = User.find(params[:id])
#first_name = #user.first_name
#last_name = #user.last_name
#wallpost = WallPost.new(params[:wall_post])
#showwallposts = #user.received_wallposts
end
def create
#wallpost = WallPost.create(params[:wall_post])
end
models
class WallPost < ActiveRecord::Base
attr_accessible :content, :receiver_id, :sender_id
belongs_to :receiver, :class_name => "User", :foreign_key => "receiver_id"
belongs_to :sender, :class_name => "User", :foreign_key => "sender_id"
end
class User < ActiveRecord::Base
has_many :sent_wallposts, :class_name => 'WallPost', :foreign_key => 'sender_id'
has_many :received_wallposts, :class_name =>'WallPost', :foreign_key => 'receiver_id'
in the view
<%= form_for(#wallpost, :url => {:action => 'create'}) do |f| %>
<%= f.hidden_field :receiver_id, :value => #user.id %>
<%= f.hidden_field :sender_id, :value => current_user.id %>
<%= f.text_area :content, :class => 'inputbox' %>
<%= f.submit 'Post', class: 'right btn' %>
<% end %>
You could create a custom validator which assures maximum DAILY_LIMIT posts have been created on that person's wall that day by that user:
class SpamValidator < ActiveModel::Validator
DAILY_LIMIT = 2
def validate(record)
if similar_posts_today(record).count >= DAILY_LIMIT
record.errors[:spam_limit] << 'Too many posts today!'
end
end
def similar_posts_today(record)
WallPost.where(receiver: record.receiver, sender: record.sender)
.where("DATE(created_at) = DATE(:now)", now: Time.now)
end
end
Then add that validation to your WallPost model:
validates_with SpamValidator
Then it will fail with a validation error when trying to create a wall post beyond the limit set in the constant. You need to handle this case in the create action in your controller. A simple (but not optimal in terms of user experience) way of handling this is:
def create
#wallpost = WallPost.new(params[:wall_post])
flash[:error] = "You've reached the daily posting limit on that wall." unless #wallpost.save
redirect_to user_path(#wallpost.receiver)
end
With that, it'll try to save the new wall post, if it is unable to, it'll set flash[:error] to the error message above. You'd need to show this on your show.html.erb page with <%= flash[:error] if flash[:error] %>.

Update nested attributes before saving to database

Long time reader of Stackoverflow but have never found myself in a position to ask a question (that hasn't already been answered). I guess there's a first time for everything so here it goes...
System Info:
Ruby Version = 1.8.7
Rails Version = 3.2.2
Situation:
We have an application with a user registration system in place. In order to hook up and populate all of our tables correctly, we are utilizing Complex/Nested Forms within the registration view. I actually have the nested forms working perfectly, everything is being populated as it should, its awesome really.
Here is the problem: I need to set one of the value of one of the nested attributes AFTER the form post but BEFORE the records are saved.
Here is a quick example so you can see what I'm talking about a little bit better:
A user registers with our site. When they register a record is created in the Users data table. Each user is also classified as a team_mate (join table) and assigned to their very own individual team (at first). But, a 'team' (table) also has an 'alias' field in it which, on the initial creation of the user we would like to set to the users first name (without having to have them enter their first name into an 'alias' field on the form).
So, I guess the question would be: How to I manually set the value of a nested attribute after the form post and before the records are saved to the database?
A (simplistic) example of the table schema looks is as follows:
Users (id, first_name, last_name, created_at, updated_at)
Team_mates(id, user_id, team_id, created_at, updated_at) - join table
Teams(id, alias, created_at, updated_at)
Models:
User.rb
class User < ActiveRecord::Base
has_many :team_mates, :dependent => :destroy
has_many :teams, :through => :team_mates, :foreign_key => :team_id
accepts_nested_attributes_for :team_mates, :allow_destroy => true
before_save :set_defaults
private
def set_defaults
#want to set :users => :team_mates_attributes => :team_attributes => :alias to #user.first_name here
# Would prefer to handle this here instead of in the controller.
end
end
Team.rb
class Team < ActiveRecord::Base
has_many :team_mates, :dependent => :destroy
has_many :users, :through => :team_mates, :foreign_key => :user_id
end
Team_mate.rb
class TeamMate < ActiveRecord::Base
belongs_to :user
belongs_to :team
accepts_nested_attributes_for :team, :allow_destroy => true
end
Controller
Users_controller.rb
class UsersController < ApplicationController
def new
#user = User.new
#user.emails.build(:is_default_email => 1)
#user.build_login
#user.team_mates.build.build_team(:alias => 'Clinton444', :created_at => Time.new, :updated_at => Time.new)
respond_to do |format|
format.html
format.json { render :json => #match }
end
end
def create
#user = User.new(params[:user])
#user.attributes = ({ "user" => { "team_mates" => { "team" => { "alias" => #user.first_name } } } }) #--this doesn't work...
#user.attributes = ({ :user => { :team_mates => { :team => { :alias => #user.first_name } } } }) #--neither does this...
respond_to do |format|
if #user.save
format.html { redirect_to(#user, :notice => 'User was successfully created.') }
format.json { render :json => #user, :status => :created, :location => #user }
else
format.html { render :action => "new" }
format.json { render :json => #user.errors, :status => :unprocessable_entity }
end
end
end
View
new.html.haml
= form_for(#user, :html => {:class => 'form-horizontal'}) do |f|
- if #user.errors.any?
.alert
%h2
= pluralize(#user.errors.count, "error")
prohibited this post from being saved:
%ul
- #user.errors.full_messages.each do |msg|
%li
= msg
%fieldset
.control-group
= f.label :first_name, :class => "control-label"
.controls
=f.text_field :first_name, :class => "span8"
.control-group
= f.label :last_name, :class => "control-label"
.controls
=f.text_field :last_name, :class => "span8"
= f.fields_for :emails do |e|
=e.hidden_field :is_default_email, :class => "span8"
.control-group
= e.label :email, :class => "control-label"
.controls
=e.text_field :email, :class => "span8"
= f.fields_for :team_mates do |tm|
= tm.fields_for :team do |t|
=t.hidden_field :alias, :class => "span8"
=t.hidden_field :created_at, :class => "span8"
=t.hidden_field :updated_at, :class => "span8"
= f.fields_for :login do |e|
.control-group
= e.label :user_login, :class => "control-label"
.controls
=e.text_field :user_login, :class => "span8"
.control-group
= e.label :password_encrypted, :class => "control-label"
.controls
=e.text_field :password_encrypted, :class => "span8"
.control-group
.controls
=f.submit :class => 'btn btn-primary btn-medium'
And finally
Rails server output on form post
Parameters: {"user"=>{"team_mates_attributes"=>{"0"=>{"team_attributes"=>{"created_at"=>"Wed Jun 06 09:52:19 -0600 2012", "alias"=>"asfs444", "updated_at"=>"Wed Jun 06 09:52:19 -0600 2012"}}}, "first_name"=>"lkjlkjlsdfslkjeowir", "last_name"=>"ouisodifuoixv", "emails_attributes"=>{"0"=>{"is_default_email"=>"1", "email"=>"lpisfsopf#psflsjdk.com"}}, "login_attributes"=>{"user_login"=>"lkjsdfooiusfd", "password_encrypted"=>"[FILTERED]"}}, "utf8"=>"✓", "commit"=>"Create User", "authenticity_token"=>"CQLQ93/0VlncSzMlmtLPHgaVrrvjuHFN+lN6CYCsiR8="}
After looking at the models you might be wondering where emails/logins are coming from. They're built within the model on our system, but are not really part of this question so I omitted the code for them. They are working, so the problem isn't on that side.
Check http://archives.ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
To support both the creation of new objects and the editing of
existing ones we have to use an array of hashes for one-to-many
associations or a single hash for one-to-one associations. If no :id
property exists then it is assumed to represent a nested model to
create.
Not 100% sure.. I haven't used\tested it before, but this should give you an idea
#user.teams.each do |team|
team.team_mates do |team_mate|
# To edit existing
team_mate.team_attributes = [ { :id => team.id, :alias => #user.first_name } ]
# To create new
team_mate.team_attributes = [ { :alias => #user.first_name } ]
team_mate.save
end
end

Configuring Rails 3 + Polymorphic Image model + Paperclip and Amazon S3, No errors, but nothing uploading

I suspect the problem lies in the way I am creating the polymorphic image attribute. I am using fields_for in the form.
In this case a user can create a post and add an image using paperclip, storing it with S3.
I am using a polymorphic image model "post_image":
class PostImage < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
#.merge(PAPERCLIP_OPS)
has_attached_file :image, :styles => { :medium => "200x200>", :thumb => "50x50>" },
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => "/:style/:id/:filename",
:bucket => "zounds-dev"
validates_attachment_presence :image
validates_attachment_size :image, :less_than => 5.megabytes
end
Post Model:
class Post < ActiveRecord::Base
has_many :post_images, :as => :imageable, :dependent => :destroy
.
.
.
accepts_nested_attributes_for :post_images, :reject_if => lambda { |t| t[:post_image].nil?}, :allow_destroy => true
end
New Post Form:
=form_for( setup_post(#post,current_user), :html => { :multipart => true}) do |f|
%dl
=f.fields_for :post_images do |ff|
=ff.file_field :image
%dt.field=f.label :name
%dd.field=f.text_field :name
%dt.field=f.label :description
%dd.field=f.text_area :description
=f.fields_for :user do |u|
=render "user_fields", :f => u
=f.fields_for :assignments do |ff|
=ff.check_box :_destroy, {:checked => ff.object.persisted?}, '0','1'
=ff.label :_destroy, ff.object.group.name
=ff.hidden_field :group_id
.action=f.submit "Save Post"
The setup_post helper method used in the Post form_for: (the groups stuff isn't relevant here)
def setup_post(post, current_user)
groups = current_user.groups_as_owner + current_user.groups_as_member
(groups - post.groups).each do |group|
post.assignments.build(:group => group)
end
post.assignments.sort_by {|x| x.group.name }
post_image = post.post_images.build
post
end
Post controller:
def new
#user = User.find(params[:user_id])
# #post = #user.posts.build
#post = Post.new
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #post }
end
end
def create
#user = current_user
#post = #user.posts.build(params[:post])
.
.
.
end
I suspect the problem is that I am using fields_for for the post_image attribute, but I've looked all over and can't figure out what the proper way to implement a polymorphic nested image attribute is.
I also did the s3sh amazon s3 console thing, and although I couldn't upload an image because I couldn't figure out how to pass in the right image path to the open() function, I connected to S3. My s3.yml file is set up correctly as well.
Thanks yall,
Brian
The issue was with the reject_if in accepts_nested_attributes for the Post model
accepts_nested_attributes_for :post_images, :reject_if => lambda { |t| t[:post_image].nil?}, :allow_destroy => true
commenting it out fixed the issue.

Calling two methods from one controller in nested model form

Through other posts on SO I've learned that my sign-up process using a nested model form is flawed in that I create a new User, then redirect to create its Profile. Here is the process:
user = User.new
user.email = ...
user.password = ...
user.profile = Profile.new
user.profile.first_name = ...
...
user.profile.save
user.save
It seems as if one solution is to initiate the profile method from within the UsersController create(?) action, so that I POST to both models(?) then redirect to a page with a form to fill out the rest of the profile.
But I'm not entirely sure how to do that, as I am new to programming/Rails. So can anyone give me guidance on how to introduce the Profile method within the UsersController? I gave it a go but don't think it's correct. Code for both Users/ProfilesController below:
User:
def new
#user = User.new
#user.profile = Profile.new
end
def index
#user = User.all
end
def create
#user = User.new(params[:user])
if #user.profile.save
redirect_to profile_new_path, :notice => 'User successfully added.'
else
render :action => 'new'
end
end
Profile:
def new
#user.profile = Profile.new
end
def create
#profile = Profile.new(params[:profile])
if #profile.save
redirect_to profile_path, :notice => 'User successfully added.'
else
render :action => 'new'
end
end
Routes.rb:
match '/signup' => 'profiles#new', :as => "signup"
get "signup" => "profiles#new", :as => "signup"
root :to => 'users#new'
resources :users
resources :profiles
My nested model form (the relevant parts):
<%= form_for(:user, :url => { :action => :create }, :html => {:id => 'homepage'}) do |f| %>
<%= f.text_field :email, :size=> 13, :id => "user[email]" %>
<%= f.fields_for :profile do |f| %>
<% end%>
<% end %>
If anyone could help me I'd greatly appreciate it.
You should have something like this in your models:
class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
...of course all backed up with proper migrations. Then while building up a form you can use fields_for helper. Here is slightly modified example from docs:
<%= form_for #user do |user_form| %>
Email: <%= user_form.text_field :email %>
<%= user_form.fields_for :profile do |profile_fields| %>
First Name: <%= profile_fields.text_field :first_name %>
<% end %>
<% end %>
And update your user and his profile in the controller in one go, thanks to accepts_nested_attributes_for :profile declaration in your model.