I have three controllers - users, stories, categories. I want when I am logged in as a administrator to create categories and then to write stories for every category. I did part of the task but in DB in table Stories category_id is empty and I cannot understand how to fix it. Here is part of my code:
stories/new.html.erb:
<%= form_for(#story) do |f| %>
<div class="field">
<%= f.label :title %><br />
<%= f.text_field :title %>
</div>
<div class="field">
<%= f.label :category %><br />
<%= f.collection_select :category_id, #categories, :id, :title %>
</div>
<div class="field">
<%= f.label :content %><br />
<%= f.text_area :content %>
</div>
<div class="actions">
<%= f.submit "Add" %>
</div>
<% end %>
stories_controller.rb:
class StoriesController < ApplicationController
before_filter :admin_user
def new
#story = Story.new
#categories = Category.all
#title = "Add news"
end
def create
#categories = Category.all
#story = current_user.stories.build(params[:story])
if #story.save
flash[:success] = "Successfullly added news"
redirect_to #story
else
#title = "Add news"
render 'new'
end
end
private
def admin_user
redirect_to(root_path) unless current_user.admin?
end
end
story.rb:
class Story < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :user
belongs_to :category
validates :title, :presence => true
validates :content, :presence => true
validates :user_id, :presence => true
default_scope :order => 'stories.created_at DESC'
end
category.rb:
class Category < ActiveRecord::Base
attr_accessible :title
has_many :stories
validates :title, :presence => true,
:length => { :within => 6..40 }
end
user.rb:
class User < ActiveRecord::Base
attr_accessor :password
attr_accessible :name, :email, :password, :password_confirmation
has_many :stories
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => true
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_save :encrypt_password
def has_password?(submitted_password)
encrypted_password == encrypt(submitted_password)
end
def self.authenticate(email, submitted_password)
user = find_by_email(email)
return nil if user.nil?
return user if user.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
private
def encrypt_password
self.salt = make_salt unless has_password?(password)
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
end
You need add attr_accessible :category_id to your story model
Its preventing mass assignment in your controllers create method. Alternatively you could pull out the category_id from your params hash and assign it on a separate line.
Related
I'm trying to resolve every n+1 issue with Bullet gem in my Rails, but I can't figure out this particular problem:
_form.html.erb
<%= simple_form_for([:admin, #course, #lesson]) do |f| %>
<div class="form-inputs">
<div class="col-md-6">
<%= f.input :start_at, minute_step: 5, start_year: Date.today.year %>
</div>
<div class="col-md-6">
<%= f.input :end_at, minute_step: 5, start_year: Date.today.year %>
</div>
<div class="col-md-6">
<!--Here is the problem-->
<%= f.association :room, :label_method => lambda { |room| "#{room.title} (#{room.building.title})"}%>
<!--Here is the problem-->
</div>
<div class="col-md-6">
<%= f.association :teacher, :label_method => lambda { |teacher| "#{teacher.first_name} #{teacher.last_name}"},
collection: #course.teachers%>
</div>
</div>
<div class="col-md-12">
<div class="form-actions text-center">
<%= f.submit class: "btn btn-primary btn-lg" %>
</div>
</div>
<% end %>
Form is rendered to new.html.erb and edit.html.erb. Problem occurs on line with room association where I'm trying to get room.building.title.
Here's the screenshoot:
As you can see, I only want to display room name with its centre name, but Bullet throws an error. Problem is that I'm using that lambda function so creating a variable in a controller doesn't help (or at least I don't know how to create it properly) since problem occurs inside select box.
Here I'm including my controller:
lessons_controller.rb
module Admin
class LessonsController < Admin::AdminController
helper_method :convert_time, :convert_day
before_action :set_course
before_action :set_lesson, only: [:show, :edit, :update, :destroy]
def index
#lessons = Lesson.includes(:teacher,:course, :room => :building).where(course_id: #course).paginate(page: params[:page], per_page: 10)
# #course_lessons = #course.lessons.paginate(page: params[:page], per_page: 10)
end
def new
##room = Room.all.includes(:building)
#lesson = Lesson.new
end
def create
#lesson = Lesson.new(lesson_params)
#lesson.course = #course
if #lesson.save
flash[:success] = "Lesson was created"
redirect_to admin_course_lessons_path(#course)
else
render 'new'
end
end
def edit
end
def update
if #lesson.update(lesson_params)
flash[:success] = "Lesson was updated"
redirect_to admin_course_lessons_path(#course)
else
render 'edit'
end
end
def show
end
def destroy
#lesson.destroy
flash[:danger] = "Lesson was deleted"
redirect_to admin_course_lessons_path(#course)
end
private
def set_course
#course = Course.find(params[:course_id])
end
def set_lesson
#lesson = Lesson.find(params[:id])
end
def lesson_params
params.require(:lesson).permit(:start_at,:end_at, :room_id, :teacher_id)
end
end
end
(Actions relevant for us in this situation are new and edit)
Here are the relevant models:
room.rb
class Room < ApplicationRecord
belongs_to :building
has_many :lessons, dependent: :destroy
before_save {self.title = title.upcase_first}
before_save {self.code = code.upcase}
validates :title, presence: true,
length: { minimum: 3, maximum: 50},
uniqueness: {case_sensitive: false}
validates :code, presence: true,
length: {minimum: 2},
uniqueness: {case_sensitive: false}
validates :building_id, presence: true
end
building.rb
class Building < ApplicationRecord
has_many :rooms, dependent: :destroy
before_save {self.title = title.upcase_first}
before_save {self.code = code.upcase}
validates :title, presence: true,
length: { minimum: 3, maximum: 50},
uniqueness: true
validates :code, presence: true,
length: {minimum: 3},
uniqueness: {case_sensitive: false}
end
Sorry for this long post and my lack of knowledge. I will appreciate any feedback.
You can specify the collection option using includes in the f.association. Like this:
f.association :room, :label_method => lambda { |room| "#{room.title} (#{room.building.title})", :collection => Room.includes(:building).all }
Im trying to submit a nested form when you create a school, you can also create a admin with it. When I try to save the form with the default value of owner set to true and status set to active, it saves the form but doesnt save those values in the database
class SchoolsController < ApplicationController
before_filter :authenticate_admin!, except: [:index, :new, :create]
def show
#school = School.friendly.find(params[:id])
end
def new
#school = School.new
#admin = #school.admins.build(:owner => true, :status => "active")
end
def create
#school = School.new(school_params)
if #school.save
redirect_to new_admin_session_path
else
render 'new'
end
end
def edit
#school = School.find_by_slug(params[:id])
end
def update
#school = School.find_by_slug(params[:id])
if #school.update_attributes(school_params)
redirect_to :action => 'show', :id => #school
else
render :action => 'edit'
end
end
def team
#teams = Admin.all
end
private
def school_params
params.require(:school).permit(:school_name, :latitude, :longitude, :radius, admins_attributes: [ :first_name, :last_name, :email, :password, :password_confirmation, :image ])
end
end
class School < ActiveRecord::Base
has_many :admins
accepts_nested_attributes_for :admins
extend FriendlyId
friendly_id :school_name, use: :slugged
def should_generate_new_friendly_id?
school_name?
end
# Validation
validates :school_name, presence: true, uniqueness: true
validates :latitude, presence: true
validates :longitude, presence: true
validates :radius, presence: true, numericality: { only_integer: true }
end
%nav.navbar.navbar-default.navbar-fixed-top{role:"navigation"}
%div.container
%div.navbar-header
%button.navbar-toggle.collapsed{"data-toggle" => "collapse", "data-target" => "#navbar", "aria-expanded" => "false", "aria-controls" => "navbar"}
%span.sr-only Toggle Navigation
%span.icon-bar
%span.icon-bar
%span.icon-bar
%a.navbar-brand{"href" => "/", "id"=> "brand"} QuickAlert
%div#navbar.collapse.navbar-collapse
%ul.nav.navbar-nav.main-menu
%li.active
%a{"href" => "#"} Home
%li
%a{"href" => "#about"} About
%li
%a{"href" => "#contact"} Contact
%div.container-fluid
%div.row
%div.school-create
- if #school.errors.any?
%ul
- #school.errors.full_messages.each do |msg|
%li
= msg
%div#map-check
%h2.header School Info
= form_for #school do |f|
= f.label :school_name, "School Name"
= f.text_field :school_name, :class => 'form-control'
= f.label :latitude, "Latitude"
= f.text_field :latitude, :class => 'form-control', :id => "latitude"
= f.label :longitude, "Longitude"
= f.text_field :longitude, :class => 'form-control', :id => "longitude"
= f.label :radius, "Radius"
= f.text_field :radius, :class => 'form-control', :id => "radius"
%div.admin-fields
%h2.header Admin Info
= f.fields_for :admins do |ff|
= ff.label :first_name
= ff.text_field :first_name, :class => 'form-control'
= ff.label :last_name
= ff.text_field :last_name, :class => 'form-control'
= ff.label :email
= ff.text_field :email, :class => 'form-control'
= ff.label :password
= ff.password_field :password, :class => 'form-control'
= ff.label :password_confirmation
= ff.password_field :password_confirmation, :class => 'form-control'
= ff.file_field :image
= f.submit :class => 'submit-button btn btn-primary'
app/models/user.rb:51:in `cart_count'
app/views/layouts/_header.html.erb:24:in `block in _app_views_layouts__header_html_erb__3312865121531214569_70179387150260'
app/views/layouts/_header.html.erb:23:in `_app_views_layouts__header_html_erb__3312865121531214569_70179387150260'
app/views/users/show.html.erb:40:in `_app_views_users_show_html_erb___592785710009336321_70179415740600'
When I attempt to view page after identification, I get the error above.
Here's my layouts/header.html.erb code:
<% if signed_in? %>
<%= link_to accueilconnect_path do%>
<li class="forma">Accueil</li>
<% end %>
<%= link_to nosformations_path do%>
<li class="contac">Formations</li>
<% end %>
<%= link_to devenirformateur_path do%>
<li class="contac">Devenir formateur</li>
<% end %>
<%= link_to contacts_path do%>
<li class="contac">Contact</li>
<% end %>
<%= link_to cart_path do%>
<i class="fi-shopping-cart"></i> My Cart (<span class="cart-count"><%=current_user.cart_count%></span>)
<%end%>
<%= link_to image_tag('user2.png', :class => "user_icon"), current_user %>
<% end %>
and here is my User Model :
class User < ActiveRecord::Base
attr_accessor :password
before_save { self.email = email.downcase }
attr_accessible :name, :email, :login, :password, :password_confirmation
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :name, :presence => true,
:length => { :maximum => 50 }
validates :email, :presence => true,
:format => { :with => email_regex },
:uniqueness => { :case_sensitive => false }
validates :login, :presence => true,
:length => { :maximum => 20 }
validates :password, :presence => true,
:confirmation => true,
:length => { :within => 6..40 }
before_create :confirmation_token
before_create :encrypt_password
# Retour true (vrai) si le mot de passe correspond.
def has_password?(password_soumis)
encrypted_password == encrypt(password_soumis)
# Compare encrypted_password avec la version cryptée de
# password_soumis.
end
def authenticate(submitted_password)
self.has_password?(submitted_password)
end
def self.authenticate_with_salt(id, cookie_salt)
user = find_by_id(id)
(user && user.salt == cookie_salt) ? user : nil
end
def email_activate
self.email_confirmed = true
self.confirm_token = nil
save!(:validate => false)
end
def cart_count
$redis.scard "cart#{id}"
end
private
def encrypt_password
self.salt = make_salt if new_record?
self.encrypted_password = encrypt(password)
end
def encrypt(string)
secure_hash("#{salt}--#{string}")
end
def make_salt
secure_hash("#{Time.now.utc}--#{password}")
end
def secure_hash(string)
Digest::SHA2.hexdigest(string)
end
def confirmation_token
if self.confirm_token.blank?
self.confirm_token = SecureRandom.urlsafe_base64.to_s
end
end
end
problem is in :
def cart_count
$redis.scard "cart#{id}"
end
any idea ?
Did you installed Redis Server:
http://redis.io/download
and Run it locallay go to where redis folder exist and write
redis-server
and define it in intalizers:
$redis = Redis.new
There are the following models:
class Discount < ActiveRecord::Base
belongs_to :content, polymorphic: true
validates :value, presence: true
end
class TimeDiscount < ActiveRecord::Base
has_one :discount, as: :content, dependent: :destroy
validates :start_time, :end_time, presence: true
accepts_nested_attributes_for :discount
end
And the following controller:
class Admin::TimeDiscountsController < ApplicationController
def new
#time_discount = TimeDiscount.new
end
def create
#time_discount = TimeDiscount.new(time_discount_params)
if #time_discount.save
redirect_to root_path
else
render 'new'
end
end
private
def time_discount_params
params.require(:time_discount).permit.tap do |whitelisted|
whitelisted[:start_time] = params[:time_discount][:start_time]
whitelisted[:end_time] = params[:time_discount][:end_time]
whitelisted[:discount_attributes] = params[:time_discount][:content]
end
end
end
Form:
= form_for #time_discount, url: admin_time_discounts_path do |f|
.row
= f.label :start_time
= f.text_field :start_time
.row
= f.label :end_time
= f.text_field :end_time
= f.fields_for :content do |discount|
.row
= discount.label :value
= discount.text_field :value
.row
= f.submit "Добавить"
But 'create' action generates 'ActiveModel::ForbiddenAttributesError' in TimeDiscount.new line. I use Rails 4. How can I fix it? Thanks.
def time_discount_params
params.require(:time_discount).permit(:start_time, :end_time, content: [:id, :start_time, :end_time])
end
This is probably what you want instead of that method you have defined in the above. This is assuming you are creating it via something like this:
= form_for #time_discount, url: admin_time_discounts_path do |f|
.row
= f.label :start_time
= f.text_field :start_time
.row
= f.label :end_time
= f.text_field :end_time
= f.fields_for :content, #time_discount.content.build do |discount|
.row
= discount.label :value
= discount.text_field :value
.row
= f.submit "Добавить"
give that a go.
I'm getting this really weird error when I submit my nested form.
Can't mass-assign protected attributes: _destroy
Any idea why this may be? It's a bit of a concern as I'm having to remove the 'destroy' hidden_field with javascript temporarily until i figure out what it is, meaning i can't delete anything!
_form.html.erb
<%= nested_form_for(#post, :html=> {:multipart => true, :class=> "new_blog_post", :id=> "new_blog_post"}) do |f| %>
<%= field do %>
<%= f.text_field :title, placeholder: "Give your post a title", :class=>"span12" %>
<% end %>
<%= field do %>
<%= f.text_area :body, placeholder: "Write something here...", :id=>"blog-text", :class=>"span12" %>
<% end %>
<%= f.label :search_locations, "Add locations to your post" %>
<%= text_field_tag :name,"",:class=>"localename", :id=>"appendedInput", :placeholder=> "Name of the location", :autocomplete => "off" %>
<%= f.link_to_add "Add a location", :locations %>
<%= actions do %>
<%= f.submit "Submit", :class=>"btn", :disable_with => 'Uploading Image...' %>
<% end end%>
_posts_controller.rb_
class PostsController < ::Blogit::ApplicationController
...
def new
#post = current_blogger.blog_posts.new(params[:post])
#location = #post.locations.build
end
def edit
#post = Post.find(params[:id])
##post = current_blogger.blog_posts.find(params[:id]) removed so any use can edit any post
#location = #post.locations.build
end
def create
location_set = params[:post].delete(:locations_attributes) unless params[:post][:locations_attributes].blank?
#post = current_blogger.blog_posts.new(params[:post])
#post.locations = Location.find_or_initialize_location_set(location_set) unless location_set.nil?
if #post.save
redirect_to #post, notice: 'Blog post was successfully created.'
else
render action: "new"
end
end
def update
#post = current_blogger.blog_posts.find(params[:id])
if #post.update_attributes(params[:post])
redirect_to #post, notice: 'Blog post was successfully updated.'
else
render action: "edit"
end
end
def destroy
#post = current_blogger.blog_posts.find(params[:id])
#post.destroy
redirect_to posts_url, notice: "Blog post was successfully destroyed."
end
location.rb
class Location < ActiveRecord::Base
after_save { |location| location.destroy if location.name.blank? }
has_many :location_post
has_many :posts, :through => :location_post
has_many :assets
attr_accessible :latitude, :longitude, :name, :post_id, :notes, :asset, :assets_attributes
accepts_nested_attributes_for :assets, :allow_destroy => true
include Rails.application.routes.url_helpers
def self.find_or_initialize_location_set(location_set)
locations = []
locations = locations.delete_if { |elem| elem.flatten.empty? }
location_set.each do |key, location|
locations << find_or_initialize_by_name(location)
end
locations
end
end
EDIT:
Snippet of rendered form in new.html.erb
<div class="row span locsearch">
<div class="input-append span3">
<input autocomplete="off" class="localename" id="appendedInput" name="name" placeholder="Name of the location" type="text" value="">
<span class="add-on"><input id="post_locations_attributes_0__destroy" name="post[locations_attributes][0][_destroy]" type="hidden" value="false"><i class="icon-trash"></i></span> </div>
<div class="latlong offset3 span4"> <p class="help-block">Enter the name of the town or city visited in this blog entry.</p>
</div>
<input class="LegNm" id="post_locations_attributes_0_name" name="post[locations_attributes][0][name]" type="hidden" value="Dresden">
<input class="long" id="post_locations_attributes_0_longitude" name="post[locations_attributes][0][longitude]" type="hidden" value="13.7372621">
<input class="lat" id="post_locations_attributes_0_latitude" name="post[locations_attributes][0][latitude]" type="hidden" value="51.0504088">
</div>
</div>
EDIT2:
post.rb
class Post < ActiveRecord::Base
require "acts-as-taggable-on"
require "kaminari"
acts_as_taggable
self.table_name = "blog_posts"
self.paginates_per Blogit.configuration.posts_per_page
# ==============
# = Attributes =
# ==============
attr_accessible :title, :body, :tag_list, :blogger_id, :coverphoto, :locations_attributes
# ===============
# = Photo Model =
# ===============
has_attached_file :coverphoto,
:styles => {
:coverbar => "600x300>", :medium => "250x250^" , :thumb => "100x100^"},
#:source_file_options => {:all => '-rotate "-90>"'},
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:bucket => "backpackbug",
:path => "/:style/:id/:filename"
# ===============
# = Validations =
# ===============
validates :title, presence: true, length: { minimum: 6, maximum: 66 }
validates :body, presence: true, length: { minimum: 10 }
validates :blogger_id, presence: true
# =================
# = Associations =
# =================
belongs_to :blogger, :polymorphic => true
has_many :location_post
has_many :locations, :through => :location_post
belongs_to :profile
accepts_nested_attributes_for :locations, :allow_destroy => true, :reject_if => proc { |attributes| attributes['name'].blank? }
end end
This was solved with a combination of this and another answer, found here:
How can I fix this create function?
A short term fix is to add attr_accessible :_destroy and attr_accessor :_destroy.
Thanks both!
Add :_destroy to your attr_accessible list
attr_accessible ...., :_destroy
you should write to post.rb,
attr_accessible: locations_atributes
and
accepts_nested_attributes_for :locations, :allow_destroy => true
as you are updating the location object via post object.
replace f.hidden_field it must stay thus(line 2)
module ApplicationHelper
def link_to_remove_fields(name, f)
text_field_tag(:_destroy) + link_to_function(name, "remove_fields(this)")
end