I'm using rails 3.2.8, globalize3 and batch_translations to translate specific content for an little cms and shop system.
I integrated it without problems for one translation on one model. So everthings works fine. I started adding this functionality for my other models and..shhhhh weird things happen.
Status now: I can create new content with translations. Everything ok. But if I try to edit/update the values in the translations tables nothing happen! Maybe there is a wrong parameter path in batch_translations or something...
Here an example for categories!
migration_file
class CreateCategories < ActiveRecord::Migration
def self.up
create_table :categories do |t|
t.timestamps
end
Category.create_translation_table! :category_name => :string, :description => :string
end
def self.down
Category.drop_translation_table!
drop_table :categories
end
end
model:
class Category < ActiveRecord::Base
attr_accessible :category_name, :description
attr_accessible :translations_attributes
translates :category_name, :description
has_many :translations
accepts_nested_attributes_for :translations
class Translation
attr_accessible :locale, :category_name, :description
end
end
this weird class Translation I wrote because I got mass-assignemnt errors for locale, etc...
form:
<div>
<%= form_for #category, html: { :multipart => true } do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= build_translation_text_field f, :category_name, :description %>
<%= f.submit (t ".save"), class: "btn btn-large btn-primary" %>
<% end %>
</div>
helper for my translation form:
def build_translation_text_field(f, *atts)
tag = content_tag(:h1, "Test")
I18n.available_locales.each do |l|
f.globalize_fields_for l do |g|
atts.each do |a|
tag += content_tag(:div, content_tag(:h4, t(a)+":"))
tag += (g.text_field a, class: l)
end
end
end
tag
end
categories_controller update method:
def create
#category = Category.new(params[:category])
if #category.save
#categories = Category.all
flash[:success] = t(:category_created)
respond_to do |format|
format.html {render 'index'}
format.js
end
else
flash[:error] = #category.errors.full_messages.each {|msg| p msg}
#categories = Category.all
respond_to do |format|
format.html {render 'new'}
format.js
end
end
end
def update
#category = Category.find(params[:id])
if #category.update_attributes(params[:category])
#categories = Category.all
flash[:success] = t(:category_updated)
respond_to do |format|
format.html {render 'index'}
format.js
end
else
flash[:error] = #category.errors.full_messages.each {|msg| p msg}
#categories = Category.all
respond_to do |format|
format.html {render 'edit'}
format.js
end
end
end
anyone an idea or have an working example with two models whit one or more translated attributes?
My fault:
Update for the model:
class Category < ActiveRecord::Base
attr_accessible :category_name, :description
attr_accessible :translations_attributes
translates :category_name, :description
# has_many :translations <-- delete this
accepts_nested_attributes_for :translations
class Translation
attr_accessible :locale, :category_name, :description
end
end
Related
I have a Rails 4.2 app which has 'Rooms', 'Bookings' and 'Extras'.
When making a booking it is for a room e.g. website.com/rooms/1/bookings/1
I have extras which I want to be associated with the booking for that room via check-boxes.
How can this be implemented? I've been reading about has_many :foo, :through => :bar associations but I'm not sure if that's the way to go.
The relevant code looks like this:
<!-- app\views\bookings\_form.html.erb -->
<%= form_for([#room, #booking]) do |f| %>
<p>
<%= f.label 'Select Customer:' %>
<%= f.collection_select :user_id, User.all, :id, :customer_name %>
</p>
<p>
<%= f.label 'start_time', 'Start Date and Time:' %>
<%= f.datetime_select :start_time, { minute_step: 15 } %>
</p>
<p>
<%= f.label 'length', 'Length of booking in hours:' %>
<%= f.number_field 'length', min: 1 %>
</p>
<p>
<%= f.label 'Room Price:' %>
<%= number_to_currency #room.price, unit: "£" %>
</p>
<p>
<%= f.label 'Extras:' %>
<%= f.collection_check_boxes :extra_ids, Extra.all, :id, :extra_info %>
</p>
<%= f.submit 'Submit' %>
<% end %>
# app\models\booking.rb
class Booking < ActiveRecord::Base
belongs_to :room
belongs_to :user
has_many :additions
has_many :extras, :through => :additions
end
# app\models\extra.rb
class Extra < ActiveRecord::Base
belongs_to :extracat
has_many :additions
has_many :bookings, :through => :additions
def extra_info
"#{name}"
end
end
# This model is for the has_many through testing I tried
# app\models\addition.rb
class Addition < ActiveRecord::Base
belongs_to :booking
belongs_to :extra
end
# Relevant section of schema
create_table "additions", force: :cascade do |t|
t.integer "booking_id"
t.integer "extra_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "bookings", force: :cascade do |t|
t.datetime "start_time"
t.datetime "end_time"
t.integer "length"
t.integer "room_id"
t.integer "user_id"
t.integer "extra_id"
end
EDIT - The section within the bookings show page.
# app\views\bookings\show.html.erb
<% #booking.extras.each do |e| %>
<%= e.name %>,
<% end %>
EDIT - Adding bookings controller
class BookingsController < ApplicationController
respond_to :html, :xml, :json
before_action :find_room
def index
#bookings = Booking.where("room_id = ? AND end_time >= ?", #room.id, Time.now).order(:start_time)
respond_with #bookings
end
def new
#booking = Booking.new(room_id: #room.id)
end
def create
#booking = Booking.new(params[:booking].permit(:room_id, :start_time, :length))
#booking.room = #room
if #booking.save
redirect_to room_bookings_path(#room, method: :get)
else
render 'new'
end
end
def show
#booking = Booking.find(params[:id])
end
def destroy
#booking = Booking.find(params[:id]).destroy
if #booking.destroy
flash[:notice] = "Booking: #{#booking.start_time.strftime('%e %b %Y %H:%M%p')} to #{#booking.end_time.strftime('%e %b %Y %H:%M%p')} deleted"
redirect_to room_bookings_path(#room)
else
render 'index'
end
end
def edit
#booking = Booking.find(params[:id])
end
def update
#booking = Booking.find(params[:id])
# #booking.room = #room
if #booking.update(params[:booking].permit(:room_id, :start_time, :length))
flash[:notice] = 'Your booking was updated succesfully'
if request.xhr?
render json: {status: :success}.to_json
else
redirect_to resource_bookings_path(#room)
end
else
render 'edit'
end
end
private
def save booking
if #booking.save
flash[:notice] = 'booking added'
redirect_to room_booking_path(#room, #booking)
else
render 'new'
end
end
def find_room
if params[:room_id]
#room = Room.find_by_id(params[:room_id])
end
end
def booking_params
params.require(:booking).permit(:user_id, :extra_id)
end
end
How is it possible to associate the extras with a booking? As so far they are not being saved with the booking into the database. Is this a controller issue?
You're not permitting the parameters correctly - the name is extra_ids. In addition since the parameter is an array you need to permit it like so:
params.require(:booking).permit(:room_id, :start_time, :length, :extra_ids => [])
Personally I recommend setting action controller to raise an error when unpermitted parameters are encountered in development or tests - very easy otherwise to miss the log messages
I am trying to automatically create a child record (Participant) when I create a parent (Project) record. I can create the parent (Project) fine and, on other forms, I can create the child (Participant). I cannot seem to create the child (Participant) at the same time as the parent.
I am on Rails 4, and so I've set my strong params carefully. I just don't understand what I'm doing wrong.
Parent Controller:
class ProjectsController < ApplicationController
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
def create_project
#project = Project.new(project_params)
#template = Template.find(params[:t])
#project.participants.build
#title = params[:ti]
respond_to do |format|
if #project.save
#project.participants.save
format.html { redirect_to new_milestones_path(:p => #project.id), notice: 'Great! We saved your project details.' }
else
format.html { redirect_to new_project_path(t: #template.id, ti: #title)
}
format.json { render json: #project.errors, status: :unprocessable_entity }
end
end
end
def project_params
params.require(:project).permit( :id, :title, :starts, participants_attributes: [:id, :email, :title, :status, :project_id])
end
end
Models:
class Participant < ActiveRecord::Base
belongs_to :project, inverse_of: :participants
........
end
class Project < ActiveRecord::Base
has_many :participants, dependent: :destroy, inverse_of: :project
accepts_nested_attributes_for :participants, allow_destroy: true, reject_if: proc { |a| a["email"].blank? }
.........
end
Form:
<%= form_for #project, url: create_project_path(ti: #title), html: { :multipart => true, :class=> "form-horizontal", id: "basicForm" }do |f| %>
<%= f.fields_for :participants do |ff|%>
<%= ff.hidden_field :email, :value => current_user.email %>
<%= ff.hidden_field :title, :value => 'Organizer' %>
<%= ff.hidden_field :status, :value => 'accepted' %>
<% end %>
<%= f.text_field :title, :placeholder => 'Your Project Title'%>
<%= f.text_field :starts, :placeholder => 'mm/dd/yyyy'%>
<%= f.submit ' SAVE PROJECT' %>
<% end %>
UPDATE:
I added #project.participants.build as Samo suggested (and I've updated my code above), which makes the fields_for visible...but my project doesn't save...it redirects back to new_project_path.
I believe I see the issue. In your new_project action, try this:
def new_project
#title = params[:ti]
#project = Project.new
#project.participants.build
end
To elaborate: fields_for is not going to render anything if the association is blank/empty. You need to have at least one participant returned by #project.participants in order to see its fields. #project.participants.build will simply insert a new instance of the Participant class into the association.
As you're using Rails 4 in your application, you don't need to call accepts_nested_attributes_for, because you are already calling params.requirein your Controller.
After #participant = Participant.new you didn't call Participant.saveaction. You do call a #project.save inside your if condition and you should do that for your #participant too. You can call #project.save before redirecting to project_path. I'm not sure if that is a correct approach, but you can try if it works. :-)
I have these models:
class List < ActiveRecord::Base
has_many :list_items, :dependent => :destroy
accepts_nested_attributes_for :list_items, :reject_if => lambda { |a| a(:task.blank?)}
end
class ListItem < ActiveRecord::Base
belongs_to :list
end
and this controller:
class ListsController < ApplicationController
def index
#lists = List.order("lists.created_at DESC")
end
def show
#list = List.find(params[:id])
end
def new
#list = List.new({:name => Time.now.strftime("%A %d %B")})
3.times { #list.list_items.build}
end
def create
#list = List.new(list_params)
if #list.save
flash[:notice] = "List successfully added."
redirect_to(:action => 'index')
else
render(:action => 'new')
end
end
def edit
#list = List.find(params[:id])
end
def update
#list = List.find(params[:id])
if #list.update_attributes
flash[:notice] = "List updated successfully."
redirect_to(:action => 'index')
else
render(:action => 'edit')
end
end
def delete
#list = List.find(params[:id])
end
def destroy
#list = List.find(params[:id]).destroy
flash[:notice] = "List successfully deleted"
redirect_to(:action => 'index')
end
private
def list_params
params.require(:list).permit(:name, :task)
end
end
and this form:
<%= form_for #list do |f| %>
<p>
<%= f.label :name %><br />
<%= f.text_field :name %>
</p>
<%= f.fields_for :list_items do |builder| %>
<p>
<%= builder.label :task %><br />
<%= builder.text_field :task %>
</p>
<% end %>
<p><%= f.submit "Create" %></p>
<% end %>
You can probably see what I am trying to do -> create a form where it creates a new list and then adds task to that list but for some reason whenever I hit submit on the form the name stays but tasks don't save?
I solved the problem turns out i needed to add the list_item attributes to the list controller. I was trying to do this the rails 3 way in teh model with attr_accessible which kept giving me an error. I needed to update the controller to have this at the bottom:
def list_params
params.require(:list).permit(:name, :list_items_attributes => :task)
end
new rails user here. I'm trying to have my schedule form store an array of "days" but after several attempts I just can't make it work.
Here are my codes currently
*schedules/_form.html.erb:*
<%= simple_form_for #schedule do |f| %>
<% if #schedule.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#schedule.errors.count, "error") %> prohibited this schedule from being saved:</h2>
<ul>
<% #schedule.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.input :section_id do %>
<%= f.select :section_id, Section.all.map{|s| [s.seccon, s.id]}, :include_blank => true %>
<% end %>
<%= f.association :subject %>
<%= f.collection_select :day_ids, #days, :id, :name, {}, {:multiple => true, :size => 1} %>
<div class="field">
<%= f.label :start_time %>
<%= f.time_select :start_time %>
</div>
<%= f.input :professor do %>
<%= f.select :professor_id, Professor.all.map{|j| [j.procon, j.id]}, :include_blank => true %>
<% end %>
<%= f.association :room %>
<%= f.button :submit %>
<% end %>
*schedules_controller.rb:*
class SchedulesController < ApplicationController
before_action :set_schedule, only: [:show, :edit, :update, :destroy]
# GET /schedules
# GET /schedules.json
def index
#schedules = Schedule.all
#days = Day.all
end
# GET /schedules/1
# GET /schedules/1.json
def show
end
# GET /schedules/new
def new
#schedule = Schedule.new
#days = Day.all
end
# GET /schedules/1/edit
def edit
end
# POST /schedules
# POST /schedules.json
def create
#schedule = Schedule.new(schedule_params)
#days = Day.all
respond_to do |format|
if #schedule.save
format.html { redirect_to #schedule, notice: 'Schedule was successfully created.' }
format.json { render action: 'show', status: :created, location: #schedule }
else
format.html { render action: 'new' }
format.json { render json: #schedule.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /schedules/1
# PATCH/PUT /schedules/1.json
def update
respond_to do |format|
if #schedule.update(schedule_params)
format.html { redirect_to #schedule, notice: 'Schedule was successfully updated.' }
format.json { head :no_content }
else
format.html { render action: 'edit' }
format.json { render json: #schedule.errors, status: :unprocessable_entity }
end
end
end
# DELETE /schedules/1
# DELETE /schedules/1.json
def destroy
#schedule.destroy
respond_to do |format|
format.html { redirect_to schedules_url }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_schedule
#schedule = Schedule.find(params[:id])
#days = Day.all
end
# Never trust parameters from the scary internet, only allow the white list through.
def schedule_params
params.require(:schedule).permit(:section_id, :subject_id, :start_time, :finish_time_id, :professor_id, :room_id, :day_ids)
end
end
schedule.rb:
class Schedule < ActiveRecord::Base
belongs_to :section
belongs_to :subject
belongs_to :finish_time
has_and_belongs_to_many :days
accepts_nested_attributes_for :days, :allow_destroy => true
validates :section_id, :subject_id, :start_time, :professor_id, :room_id, :presence => true
belongs_to :professor
belongs_to :room
end
day.rb:
class Day < ActiveRecord::Base
has_and_belongs_to_many :schedules
default_scope { order(:id)}
has_paper_trail
validates :name, :desc, :presence => true
end
As was said here, the best thing to do is to create a has_many model relationship between Schedule and Day. You'll need a separate join table to make the relationship work. It will have: schedule_id and day_id as the two columns. You'd do this because you have a many > many relationship. There can be many schedules that belong to a day and many days that belong to a schedule.
I used this scenario in my app:
Recipe.rb
class Recipe < ActiveRecord::Base
has_and_belongs_to_many :wines
default_scope { order(:name) }
end
Wine.rb
class Wine < ActiveRecord::Base
has_and_belongs_to_many :recipes
accepts_nested_attributes_for :recipes, :allow_destroy => true
end
Migration
class AddRecipesWinesJoinTable < ActiveRecord::Migration
def self.up
create_table :recipes_wines, :id => false do |t|
t.column :recipe_id, :integer, :null => false
t.column :wine_id, :integer, :null => false
end
add_index :recipes_wines, [:wine_id]
end
def self.down
remove_index :recipes_wines, [:wine_id]
drop_table :recipes_wines
end
end
_wine_form.html.erb
# #recipes is Recipe.all generated by the controller
<%= w.collection_select :recipe_ids, #recipes, :id, :name, {}, {:multiple => true, :size => 6, :style => 'width:100%'} %>
Hope this helps.
I stuck with file uploading.
I'm use a complex form. Here is a model:
class Project < ActiveRecord::Base
has_one :task
accepts_nested_attributes_for :task
end
class Task < ActiveRecord::Base
has_one :project
accepts_nested_attributes_for :project
end
Here is a view:
<%= form_for(#project) do |f| %>
<div class="field">
<%= f.label :name %><br />
<%= f.text_field :name %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<%= f.fields_for :task_attributes do |a| %>
<div class="field">
<%= a.label :name %><br />
<%= a.text_field :name %>
<%= form_tag 'project/upload', :multipart => true do %>
<label for="file">Upload text File</label><%= a.file_field :path %>
<% end %>
</div>
<% end %>
<% end %>
Here is a controller:
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.build_task
respond_to do |format|
format.html # new.html.erb
format.xml { render :xml => #project }
end
end
def upload
uploaded_io = params[:upload][:path]
File.open(Rails.root.join('public', 'uploads', uploaded_io.original_filename), 'w') do |file|
file.write(uploaded_io.read)
end
end
def create
#project = Project.new(params[:project])
respond_to do |format|
if #project.save
format.html { redirect_to(#project, :notice => 'Project was successfully created.') }
format.xml { render :xml => #project, :status => :created, :location => #project }
else
format.html { render :action => "new" }
format.xml { render :xml => #project.errors, :status => :unprocessable_entity }
end
end
end
end
Migration:
class CreateProjects < ActiveRecord::Migration
def self.up
create_table :projects do |t|
t.integer :id
t.string :name
t.timestamps
end
end
def self.down
drop_table :projects
end
end
class CreateTasks < ActiveRecord::Migration
def self.up
create_table :tasks do |t|
t.string :name
t.integer :project_id
t.string :path
t.timestamps
end
end
def self.down
drop_table :tasks
end
end
When I create new project with task via form, I have both records in database. I see task file path in database. But I can't see file in my public directory. I can not figure out how to save the file to public directory.
Please, help me to solve this problem!
You cannot have a nested form in html. You have nested forms. On submitting a form, it will send the data to server as one request and one action within a controller will be called. In your case on submit of the form the create action will be called and not the upload action wont be called.
Get rid of the inner form and add multipart to the outer form. It would suffice for your case.
It is an overhead to handle file uploads manually. I would suggest you to use paperclip or carrierwave. Anyways that is not your problem here.