LoadError in Controller#index - ruby-on-rails

I created a polymorphic relation in a book reviewing app that I am writing. My app has the nested models: Piece >> Section >> Subsection >> Subsubsection, and I have created a model which belongs to all of these called KeyConcept, my intention being that each of these can have a key concept.
I am getting an error that I don't understand when trying to display the index action of the keyconcepts controller.
I think it might be due to a naming conflict but I don't have enough experience to understand it.
the error I am getting looks like this:
Unable to autoload constant KeyConceptsController, expected /home/david/Documents/Learning/StuddyBuddy/app/controllers/key_concepts_controller.rb to define it
else
require_or_load(expanded, qualified_name)
raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false)
return from_mod.const_get(const_name)
end
elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix)
My keyconcepts controller looks like this:
key_concepts_controller.rb
class Key_conceptsController < ApplicationController
include KeyconceptsHelper
def whereami
if params[:piece_id]
#piece = Piece.find(params[:piece_id])
#keyconcept = #piece.key_concepts.find(params[:id])
here = #piece
parameter = :piece_id
type = "Piece"
elsif params[:section_id]
#section = Section.find(params[:section_id])
#piece = #section.piece_id
#keyconcept = #section.key_concepts.find(params[:id])
here = #section
parameter = :section_id
type = "Section"
elsif params[:subsection_id]
#subsection = Subsection.find(params[:subsection_id])
#section = #subsection.section_id
#piece = Section.find(id=#section).piece_id
here = #subsection
parameter = :subsection_id
type = "Subsection"
elsif params[:subsubsection_id]
#subsubsection = Subsubsection.find(params[:subsubsection_id])
#subsection = #subsubsection.subsection_id
#section = Subsection.find(id=#subsection).section_id
#piece = Section.find(id=#section).piece_id
here = #subsubsection
parameter = :subsubsection_id
type = "Subsubsection"
end
end
def redirect
if type == "Piece"
#redirect = redirect_to piece_path(#piece)
elsif type == "Section"
#redirect = redirect_to piece_section_path(#piece, #section)
elsif type == "Subsection"
#redirect = redirect_to piece_section_subsection_path(#piece, #section, #subsection)
elsif type == "Subsubsection"
#redirect = redirect_to piece_section_subsection_subsubsection_path(#piece, #section, #subsection, #subsubsection)
end
end
def index
whereami.call
#piece = Piece.find(params[:piece_id])
#keyconcept = #piece.key_concepts.find(params[:id])
#redirect = redirect.call
end
def show
whereami.call
redirect.call
end
def new
#keyconcept = KeyConcept.new
#keyconcept.conceptable_id = here.id
end
def create
whereami.call
#keyconcept = KeyConcept.new(keyconcept_params)
#keyconcept.conceptable_id = params[parameter]
#keyconcept.conceptable_type = type
#keyconcept.save
redirect.call
end
def destroy
here.destroy
redirect.call
flash.notice = "#{type} '#{here.name}' from '#{#piece.name}' deleted!"
end
def edit
whereami.call
end
def update
whereami.call
here.update(keyconcept_params)
flash.notice = "#{type} '#{here.name}' Updated!"
redirect.call
end
end
the link comes from the show action of the parent Piece model here:
<% #piece.key_concepts.each do |concept| %>
<li>
<%= link_to concept.definition, [#piece, #keyconcept] %>
<!-- we didn't use #piece_key_concept_path(#piece, #keyconcept), class: 'section_name' and it worked -->
</li>
How do I link to the keyconcepts "show" action by the way? I havent been able to so i just linked to the index action :/
So the routes.rb file looks like this:
resources :pieces do
resources :sections do
resources :subsections do
resources :subsubsections
end
end
resources :links
end
resources :pieces, :sections, :subsections, :subsubsections do
resources :connections, only: [:index, :new, :edit, :update, :destroy, :create]
resources :keyconcepts, only: [:index, :new, :edit, :update, :destroy, :create, :show]
end
key_concept.rb
class KeyConcept < ActiveRecord::Base
belongs_to :conceptable, polymorphic: true
end
piece.rb
class Piece < ActiveRecord::Base
include Connectable
include Conceptable
has_many :sections
has_many :subsections, through: :sections
has_many :links
end
in models/concerns/conceptable.rb
module Conceptable
extend ActiveSupport::Concern
included do
has_many :keyconcepts, as: :conceptable
end
end

Well the problem is naming convention
Your key_concepts_controller class name should be
KeyConceptsController < ApplicationController
Also make sure you follow proper conventions
If your model name is KeyConcept file name must be key_concept.rb
Controller name should be KeyConceptsController and file name must be key_concepts_controller.rb
Same goes with routes
resources : key_concepts
Refer this for more details

Related

How to set karma to appropriate user

I'm trying to add a user karma feature to my app and I'm almost done, just that the karma is being awarded to a different user.
NB, My like system is from scratch and not acts_as_votable.
What I want:
When a user upvotes a book, I want a +1 karma be awarded to the
book.user
If a user's books are downvoted more then they upvoted, I want such
user have negative karma.
What I'm getting:
When a book is upvoted, the user who upvoted the book gets the +1
karma instead of the book.user.
When a user with 0 karma gets his/her book downvoted, the karma incrment by 1 instead of decrementing.
class AddKarmaToUsers < ActiveRecord::Migration[6.0]
def change
add_column :users, :karma, :integer, default: 0
end
end
My code:
vote.rb
class Vote < ApplicationRecord
belongs_to :user
belongs_to :book
validates_uniqueness_of :user_id, scope: :book_id
after_create :increment_vote, :add_karma
after_destroy :decrement_vote, :remove_karma
private
def increment_vote
field = self.upvote ? :upvotes : :downvotes
Book.find(self.book_id).increment(field).save
end
def decrement_vote
field = self.upvote ? :upvotes : :downvotes
Book.find(self.book_id).decrement(field).save
end
def add_karma
user = User.find(self.user_id)
user.increment(:karma, 1).save
end
def remove_karma
user = User.find(self.user_id)
user.decrement(:karma, 1).save
end
end
votes_controller.rb
class VotesController < ApplicationController
def create
book_id = params[:book_id]
vote = Vote.new
vote.book_id = params[:book_id]
vote.upvote = params[:upvote]
vote.user_id = current_user.id
#check if vote by this user exists
existing_vote = Vote.where(user_id: current_user.id, book_id: book_id)
#new_vote = existing_vote.size < 1
respond_to do |format|
format.js {
if existing_vote.size > 0
#destroy existing vote
existing_vote.first.destroy
else
#save new vote
if vote.save
#success = true
else
#success = false
end
# #total_upvotes = #book.upvotes
# #total_downvotes = #book.downvotes
end
#book = Book.find(book_id)
#is_upvote = params[:upvote]
render "votes/create"
}
end
end
private
def vote_params
params.require(:vote).permit(:upvote, :book_id)
end
end
First of all when using active record relations you don't need to call Model.find in the class, just call the relation with it's name:
def increment_vote
field = self.upvote ? :upvotes : :downvotes
book.increment(field).save
end
def add_karma
user.increment(:karma, 1).save
end
In add_karma and remove_karma you are referencing the user that the vote belongs to, and not the user that owns the book. To achieve your goal you should also increment and decrement karma on the book's owner:
def add_karma
user.increment(:karma, 1).save
book.user.increment(:karma, self.upvote ? 1 : -1).save
end
def remove_karma
user.increment(:karma, 1).save
book.user.decrement(:karma, 1).save
end
You could rewrite your controller to simplify the code:
class VotesController < ApplicationController
def create
#vote = current_user.votes.find_or_initialize_by vote_params[:book_id]
#vote.assign_attributes vote_params
#success = #vote.save
# instead of #book = #vote.book just use #vote.book in your view
#book = #vote.book
# instead of #is_upvote you can use #vote.upvote in your view
#is_upvote = #vote.upvote
respond_to do |format|
format.js { render 'votes/create'}
end
end
private
def vote_params
params.require(:vote).permit(:upvote, :book_id)
end
end

dup or clone action in rails

Well im going to clarify im doing it
class DuplicatesController < ApplicationController
before_action :set_venue, only: [:new]
def new
end
def create
if #venue.duplicate(venue_params)
flash[:success] = t('controller.create.success',
resource: Venue.model_name.human)
redirect_to admin_venue_url #venue
else
flash[:warning] = #venue.errors.full_messages.to_sentence
render :new
end
end
private
def set_venue
#venue = Venue.friendly.find params[:venue_id]
end
end
def venue_params
params.require(:venue).permit(:name,
:address,
:address_complement,
:city,
:phone)
end
end
def duplicate
(name,
address,
address_complement,
city,
phone)
new_venue = self.dup
new_venue.update_attributes(description: self.description,
logo: self.logo,
opening_time: self.opening_time,
closing_time: self.closing_time,
ally_id: self.ally_id)
new_venue.save
end
How can I call those params in my duplicates controller, thanks
I need to set the attributes, after create a dup because I want to save a new record with new information, but i dont know to do it in my method, someone could explain me
Thanks.
Probably the best way to do it is to pass only id/slug of original model.
Then your duplicates_controller.rb can look similar to this:
class DuplicatesController < ApplicationController
def create
old_venue = Venue.friendly.find(params[:id])
venue = old_venue.dup
venue.attributes = venue_params
if venue.save
# success render
else
# error render
end
end
private
def venue_params
params.require(:venue).permit(:permitted_attributes) # id should not be there
end
end
Of course you can refactor it, but I do not think it is needed in this situation.
Or my favourite is to change VenueController#create to something like this to allow creating from another instance:
if copy_from_id = params[:copy_from_id]
#copy_source = Venue.find_by(id: copy_from_id)
#venue = #copy_source.dup
#venue.attributes = venue_params
else
#venue = Venue.new
end
if #resource.save
...
else
...
end

Manually assigning parent ID with has_many/belongs_to association in custom class

I'm using a custom class to make AR instances from Feedjirra. I can't get the children instances to relate to their parent objects.
Show has_many :episodes -
Episode belongs_to :show -
show_id is always nil.
RSpec logs #show.id and #episode.show_id as equal to one another. However when I run episode = Episode.first after running an import in development, the episode has its show_id set to nil.
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
...
if #show.save
puts "#show.id: #{#show.id}"
end
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
...
if #episode.save
puts "#episode.show_id: #{#episode.show_id}"
end
end
I tried using #episode = #show.episodes.create, as well as #episode = Episode.new with #episode.show_id = #show.id. They all log matching IDs but show_id is still nil on the instances. Every other column is filled in correctly.
I thought the issue may have had to do with using add_foreign_key:
class AddShowToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true
add_foreign_key :episodes, :shows, column: :show_id
end
end
So I removed that and used the standard foreign_key: true but it had no effect.
class RemoveShowFromEpisodes < ActiveRecord::Migration
def change
remove_column :episodes, :show_id
end
end
class AddShowBackToEpisodes < ActiveRecord::Migration
def change
add_reference :episodes, :show, index: true, foreign_key: true
end
end
Here's the full code in case it helps.
importers_controller.rb:
class Admin::ImportersController < Admin::ApplicationController
before_action :set_importer, only: [:show, :edit, :update, :destroy]
def index
#importers = policy_scope(Importer)
end
def show
end
def new
#importer = Importer.new
authorize #importer
end
def create
#importer = Importer.new(importer_params)
authorize #importer
if #importer.save
require "subscription_importer"
SubscriptionImporter.new(#importer)
flash[:notice] = "Importer added."
redirect_to admin_importers_path
else
flash[:error] = "Importer not added."
render "new"
end
end
def edit
end
def update
end
def destroy
end
private
def set_importer
#importer = Importer.find(params[:id])
authorize #importer
end
def importer_params
params.require(:importer).permit(:name, :url, :source)
end
end
subscription_importer.rb:
class SubscriptionImporter
def initialize(importer)
#importer = importer
#feed = Feedjira::Feed.fetch_and_parse #importer.url
if #importer.source === "iTunes"
itunes_parser(#importer)
end
end
def itunes_parser(importer)
#importer = importer
# Parser
#feed = Feedjira::Feed.fetch_and_parse #importer.url
# Show
#show = Show.new
#show.name = #feed.title
#show.description = #feed.description
#show.logo = #feed.itunes_image
#show.explicit = explicit_check(#feed.itunes_explicit)
#show.genre = #feed.itunes_categories
#show.tags = #feed.itunes_keywords
#show.url = #feed.url
#show.language = #feed.language
if #show.save
puts "Show import succeeded"
puts "#show.id: #{#show.id}"
else
puts "Show import failed"
end
# Episodes
#episodes = []
#feed.entries.each do |item|
#episodes.push(item)
end
#episodes.each do |item|
#episode = #show.episodes.new
#episode.name = item.title
#episode.description = item.summary
#episode.release = item.published
#episode.image = item.itunes_image
#episode.explicit = explicit_check(item.itunes_explicit)
#episode.tags = item.itunes_keywords
#episode.url = item.enclosure_url
#episode.duration = item.itunes_duration
if #episode.save
puts "Episode import succeeded"
puts "#episode.show_id: #{#episode.show_id}"
else
puts "Episode import failed"
end
end
end
def explicit_check(string)
if string == "yes" || "Yes"
true
else
false
end
end
end
create_importer_spec.rb:
require "rails_helper"
RSpec.feature "Admins can create importers" do
let(:user) { FactoryGirl.create(:user, :admin) }
context "admins" do
before do
login_as(user)
visit "/"
click_link "Admin"
click_link "Importers"
click_link "New Importer"
end
scenario "with valid credentials" do
fill_in "Name", with: "The Stack Exchange Podcast"
fill_in "Url", with: "https://blog.stackoverflow.com/feed/podcast/" # Needs stubbing
select "iTunes", from: "Source"
click_button "Create Importer"
expect(page).to have_content "Importer added"
expect(page).to have_content "The Stack Exchange Podcast"
end
scenario "with invalid credentials" do
fill_in "Name", with: ""
fill_in "Url", with: ""
click_button "Create Importer"
expect(page).to have_content "Importer not added"
end
end
end
I think the episodes functionality in your SubscriptionImporter class is causing the problem...
#episodes = []
#feed.entries.each do |item|
#episodes.push(item) #-> each "#episodes" is a FeedJirra object
end
#episodes.each do |episode|
#-> you're now creating an episode in the same call as show, which will either mean that show is not persisted or perhaps some other error
end
I would personally limit the SubscriptionImporter functionality to only return data. You should be parsing that data through the respective models:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
if #import.save
#import.parse_show if #import.itunes?
end
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
def feed
return false unless itunes?
origin = Feedjirra::Feed.fetch_and_parse(self.url)
return {
name: origin.title,
description: origin.description,
logo: origin.itunes_image,
explicit: explicit_check(origin.itunes_explicit),
genre: origin.itunes_categories,
tags: origin.itunes_keywords,
url: origin.url,
language: origin.language,
entries: origin.entries
}
end
def parse_show
Show.create(feed)
end
def itunes?
self.source == "iTunes" #-> true/false
end
private
def explicit_check
string == "yes" || "Yes" #-> true/false
end
end
#app/models/show.rb
class Show < ActiveRecord::Base
has_many :episodes
attr_accessor :entries
after_create :create_episodes #-> might not persist entries
def create_episodes
if self.entries.any?
self.entries.each do |item|
self.episodes.create({
name: item.title
description: item.summary,
release: item.published,
image: item.itunes_image,
explicit: explicit_check?(item.itunes_explicit),
tags: item.itunes_keywords,
url: item.enclosure_url,
duration: item.itunes_duration
})
end
end
end
private
def explicit_check?
string == "yes" || "Yes"
end
end
The above will allow you to create an #importer, pull the feed from it, and populate Show & Episode models with the returned data.
Whilst this should resolve your issue, you need to consider OOP -- making each element an object.
Update
If you wanted to objectify this even more, there is a simple pattern to adopt:
Importer is all you need to save -- everything else should happen around this
Show + Episode could be the same class / table for all I know
With this in mind, you could do the following:
#app/controllers/admin/importers_controller.rb
class Admin::ImportersController < Admin::ApplicationController
def create
#import = Importer.new import_params
#import.save
end
private
def import_params
params.require(:importer).permit(:name, :url, :source)
end
end
#app/services/feed.rb
class Feed
attr_reader :params, :show, :episode, :origin
def initialize(params)
#params = params
end
def origin
#origin = Feedjirra::Feed.fetch_and_parse params[:source]
end
def show
#show = ShowHelper.new #origin
end
def episodes
#show.episodes
end
end
#app/services/show_helper.rb
class ShowHelper
attr_reader :origin
def initialize(origin)
#origin = origin
end
def name
#origin.title
end
def description
#origin.summary || #origin.description
end
def logo
#origin.itunes_image
end
def explicit
%r{^yes$} =~ #origin.itunes_explicit
end
def genre
#origin.itunes_categories
end
def tags
#origin.itunes_keywords
end
def url
#origin.url
end
def language
#origin.language
end
def episodes
#origin.entries
end
end
#app/models/importer.rb
class Importer < ActiveRecord::Base
after_create :parse_show, if: "itunes?"
validates :source, :url, :name, presence: true
def itunes?
source == "iTunes"
end
def feed
#feed = Feed.new(self)
end
private
def parse_show
#show = Show.new(feed.show) if feed && feed.show
if #show.save && #show.entries.any?
#show.entries.each do |entry|
#show.episodes.create ShowHelper.new(entry)
end
end
end
end

How do I update Nested Attributes in Rails without accepts_nested_attributes_for?

I'm working on a project for a class where I have a very large form with Nested Models. Here are the models that are important to the form, as well as their associations:
Course: has_many :time_blocks, has_many :tags, through: :taggings, belongs_to :institution, has_many :roles, has_many :users, through: :roles
TimeBlock: belongs_to :course
Tag: has_many :taggings
Tagging: belongs_to :tag, belongs_to :taggable_type
Institution: has_many :courses, has_many :users
Role: belongs_to :course, belongs_to :user
I am able to create the Nested Form correctly, but I can't get the Nested Models to update correctly. Here is the controller, the form is very long, but I have provided the params for the Nested Models. Note, I cleared out the values from the params, but some of the params have ID values because they exist in the db. I've also included the CoursesHelper to show the helper methods I'm using in the controller.
app/controllers/courses_controller.rb
def new
#course = current_user.courses.new
#course.institution = Institution.new
4.times { #course.tags.build }
7.times { #course.time_blocks.build }
end
def create
#course = Course.new(params[:course])
#course.institution = Institution.new(params[:institution])
filled_tags = set_tags(params[:tag])
#course.tags.build(filled_tags)
filled_time_blocks = set_time_blocks(params[:time_block])
#course.time_blocks.build(filled_time_blocks)
if #course.save
Role.create!(
user_id: current_user.id,
course_id: #course.id,
title: 'instructor'
)
redirect_to #course
else
(4 - filled_tags.count).times { #course.tags.build }
(7 - filled_time_blocks.count).times { #course.time_blocks.build }
flash.now[:errors] = #course.errors.full_messages
render :new
end
end
def edit
end
def update
filled_time_blocks = set_time_blocks(params[:time_block])
filled_time_blocks.each do |time_block|
#course.time_blocks.update_attributes(time_block)
end
filled_tags = set_tags(params[:tag])
filled_tags.each { |tag| #course.tags.update_attributes(tag) }
# #course.tags.update_attributes(filled_tags)
# #course.time_blocks.update_attributes(filled_time_blocks)
fail
if #course.update_attributes(params[:course])
redirect_to #course
else
flash.now[:errors] = #course.errors.full_messages
render :edit
end
end
app/helpers/courses_helper.rb
def set_time_blocks(entries)
result = []
days = entries[:day_of_week].reject! { |day| day.blank? }
days.each do |day|
time_block = {}
time_block[:day_of_week] = day
time_block[:start_time] = entries[day][:start_time]
time_block[:end_time] = entries[day][:end_time]
time_block[:id] = entries[day][:id]
result << time_block
end
result
end
def set_tags(entries)
[].tap do |tags|
entries.each do |entry|
tags << entry unless entry.values.all?(&:blank?)
end
end
end
def find_course
if params.include?(:id)
#course = Course.find(params[:id])
else
flash[:notice] = "Sorry, Could Not Find Course."
redirect_to current_user
end
end
TimeBlock Params
{"sun"=>{"start_time"=>"", "end_time"=>"", "id"=>""}, "mon"=>{"start_time"=>"", "end_time"=>"", "id"=>"3"}, "tue"=>{"start_time"=>"", "end_time"=>"", "id"=>"4"}, "wed"=>{"start_time"=>"", "end_time"=>"", "id"=>"5"}, "thu"=>{"start_time"=>"", "end_time"=>"", "id"=>"6"}, "fri"=>{"start_time"=>"", "end_time"=>"", "id"=>"7"}, "sat"=>{"start_time"=>"", "end_time"=>"", "id"=>""}, "day_of_week"=>[]}
Tag Params
[{"name"=>"", "id"=>"4"}, {"name"=>"", "id"=>""}, {"name"=>"", "id"=>""}, {"name"=>"", "id"=>""}]
If you cant make it work with accepts_nested_attributes_for then you'll have to write your own setter method(s) manually. Something like:
class Course < ActiveRecord::Base
def tag_attributes=(tags)
tags.each do |tag|
self.tags.build(tag)
end
end
end
The method name (tag_attributes= in my example) needs to match the key name that the tag params are listed under

confused, where are the defintions of show and index actions?

I'm looking at this code, I can see that is says actions: show and index but where are the methods show and index??
http://github.com/railsdog/spree/blob/master/core/app/controllers/products_controller.rb
class ProductsController < Spree::BaseController
HTTP_REFERER_REGEXP = /^https?:\/\/[^\/]+\/t\/([a-z0-9\-\/]+\/)$/
#prepend_before_filter :reject_unknown_object, :only => [:show]
before_filter :load_data, :only => :show
resource_controller
helper :taxons
actions :show, :index
private
def load_data
load_object
#variants = Variant.active.find_all_by_product_id(#product.id,
:include => [:option_values, :images])
#product_properties = ProductProperty.find_all_by_product_id(#product.id,
:include => [:property])
#selected_variant = #variants.detect { |v| v.available? }
referer = request.env['HTTP_REFERER']
if referer && referer.match(HTTP_REFERER_REGEXP)
#taxon = Taxon.find_by_permalink($1)
end
end
def collection
#searcher = Spree::Config.searcher_class.new(params)
#products = #searcher.retrieve_products
end
def accurate_title
#product ? #product.name : nil
end
end
My guess is that the actions method is loaded with resource_controller as a module from the lib directory. Then calling the actions method creates the index and show methods.
The class inherits from Spree::BaseController and ActionController. Spree::BaseController has method action which takes method names as messages.

Resources