I'm still really new to Rails and working on a practice project lab and I ran into an issue where the view was rendering really slow.
Any and all help would be appreciated!
I also received this error:
SystemStackError (stack level too deep):
app/models/blogger.rb:23:in `popular_post'
app/views/bloggers/show.html.erb:13:in `_app_views_bloggers_show_html_erb__247812415140953454_70240453389320'
Gemfile
gem 'rails', '~> 5.1.6'
gem 'sqlite3'
gem 'puma', '~> 3.7'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'faker'
gem 'coffee-rails', '~> 4.2'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
group :development, :test do
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
gem 'capybara', '~> 2.13'
gem 'selenium-webdriver'
end
group :development do
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
My classes are Blogger, Destination, and Post (Blogger -< Post >- Destination). Below is the code for the Blogger model, show page, and controller (I didn't work on code for anything else yet, except the Blogger index but works and is just a list)
blogger.rb
class Blogger < ApplicationRecord
has_many :destinations
has_many :posts, through: :destinations
validates :name, uniqueness: true
validates :age, numericality: { greater_than: 0, message: ": You're Too Young" }
validates :bio, length: { :minimum => 30 }
def self.average_age
ages = self.all.sum do |blogger|
blogger.age
end
(ages / self.all.count)
end
def total_likes
self.posts.sum do |post|
post.likes
end
end
def popular_post
self.posts.max_by do |a|
a.likes
end
end
def top_five
self.destinations.sort_by do |destination|
destination.post_count
end.take(5)
end
end
blogger show page
<h1> <%= #blogger.name %> </h1>
<br />
<p> Age: <%= #blogger.age %> </p>
<br />
<br />
<p> Bio: <%= #blogger.bio %> </p>
<br />
<br />
<%= link_to #blogger.popular_post.title, post_path(#blogger.popular_post) %>
<br />
<%= #blogger.top_five %>
blogger controller
class BloggersController < ApplicationController
def index
#bloggers = Blogger.all
end
def show
#blogger = Blogger.find(params[:id])
end
def new
#blogger = Blogger.new
end
def create
#blogger = Blogger.create(blogger_params)
if #blogger.valid?
#blogger.save
redirect_to blogger_path(#blogger)
else
flash[:my_errors] = #blogger.errors.full_messages
redirect_to new_blogger_path
# render :new
end
end
private
def blogger_params
params.require(:blogger).permit(:name, :bio, :age)
end
end
I think that you need to modify this method
def popular_post
self.posts.max_by do |a|
a.likes
end
end
max_by is a method for an Enumerable class and you call it on a Post class, so this will not work properly. I would suggest that you use something like posts.order('likes DESC').first if likes is an Integer.
Also tiny hint you don't need to use all this self in an object and in class methods. Rails would assume that you call it on an object if this is an object method or you call it on a relation if this is a class method.
The methods total_likes, popular_post and top_five all work by assuming your associations are "arrays" (enumerations). While this works, definitally for smaller examples, it is less optimal. Because working on an array also requires the complete array to be loaded from the database first, and then do your calculations locally on a complete set (in memory). A database is optimally suited to perform such tasks (querying).
For instance, top_five can better be written to sort on the database and then only fetch the 5 first (most popular).
def top_five
destinations.order(post_count: :desc).limit(5)
end
(this is assuming that :post_count is an actual field in the database.
Similarly we can improve the other methods:
def total_likes
posts.sum(:likes)
end
just use the database: this will start a single query to return the sum of likes for all the posts of the blogger.
def popular_post
posts.order(likes: :desc).limit(1).first
end
We sort on descending likes and the select the first one. The limit 1 tells we only want to retrieve one item, but this still returns an array, so we want the first returned item.
This should improve your performance.
Related
I'm developing Web application, with Ruby on Rails, which is to add text to image uploaded by user. Right now I have no idea how to edit image with minimagick. What I want to ask is that how to get and edit the image which is uploaded to active_storage.
Code I wrote is here:
Gemfile
ruby '2.5.3'
gem 'rails', '~> 5.2.2'
gem 'mysql2', '>= 0.4.4', '< 0.6.0'
gem 'puma', '~> 3.11'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.2'
gem 'turbolinks', '~> 5'
gem 'jbuilder', '~> 2.5'
gem 'bootsnap', '>= 1.1.0', require: false
gem 'slim-rails'
gem 'html2slim'
gem 'carrierwave'
gem "mini_magick"
index.html.slim
.text
p Upload Image
= form_for #picture, url: {action: "create"} do |f|
= f.file_field :image
= f.submit 'Submit'
pictures_controller.rb
class PicturesController < ApplicationController
def index
#picture = Picture.new
end
def show
#picture = Picture.find_by(uuid: params[:id])
end
def create
text = 'this is test text'
image = picture_params[:image]
#picture = Picture.new(uuid: SecureRandom.uuid, image: picture_params[:image])
new_image = #picture.build_collage_image(text).tempfile.open.read
send_data new_image, :type => 'image/png', :disposition => 'inline'
end
private
def picture_params
params.require(:picture).permit(:image, :ogp)
end
end
models/picture.rb
class Picture < ApplicationRecord
include CollageMaker
include Rails.application.routes.url_helpers
after_save :build_collage_image
has_one_attached :collaged_image
has_one_attached :image
def build_collage_image(text)
CollageMaker.build(url_for(self.image), text)
end
end
concerns/collage_maker.rb
module CollageMaker
extend ActiveSupport::Concern
include Rails.application.routes.url_helpers
FONT = './app/assets/fonts/cinecaption226.ttf'.freeze
GRAVITY = 'center'
TEXT_POSITION = '0,0'
FONT_SIZE = 65
INDENTION_COUNT = 16
ROW_LIMIT = 8
def self.build(image_url, text)
text = prepare_text(text)
image = MiniMagick::Image.open(image_url)
image.combine_options do |config|
config.font FONT
config.fill 'white'
config.gravity GRAVITY
config.pointsize FONT_SIZE
config.draw "text #{TEXT_POSITION} '#{text}'"
end
end
private
def self.prepare_text(text)
text.to_s.scan(/.{1,#{INDENTION_COUNT}}/)[0...ROW_LIMIT].join("\n")
end
end
When test them, got this error.
The url_for is the view context path to the image.
To access the image internally, you need to use rails_blob_path
From the documentation...
If you need to create a link from outside of controller/view context
(Background jobs, Cronjobs, etc.), you can access the rails_blob_path
like this:
Rails.application.routes.url_helpers.rails_blob_path(user.avatar,
only_path: true)
https://edgeguides.rubyonrails.org/active_storage_overview.html
I'm a Rails newbie and I'm taking the Complete Ruby on Rails Developer Course on Udemy. I'm getting the following error when I try to go to the /search_stocks route.
Here's the repo in its current state (I also have the code pasted below): https://github.com/sarahbasinger/rails-stock-tracker
Here's the Udemy course repo:
https://github.com/udemyrailscourse/finance-tracker
The TA for the course suggests it might be a gem version conflict. I'm using Rails 5.1.4 (maybe a newbie mistake - I thought using the latest and greatest would be a good way to go). The teacher in the course is using Rails 4. The TA suggested I use the same gem versions as the course, so I updated my Gemfile to match the course Gemfile, ran bundle install, and with that, I can't even get the rails server to run. I get a different error. So I'm back to trying to get this app running using Rails 5. However, I have no experience trying to resolve gem version conflicts, if that is the issue.
Here's the relevant code:
Model
class Stock < ActiveRecord::Base
def self.new_from_lookup(ticker_symbol)
looked_up_stock = StockQuote::Stock.quote(ticker_symbol)
new(name: looked_up_stock.name, ticker: looked_up_stock.symbol, last_price: looked_up_stock.l)
end
end
Controller
class StocksController < ApplicationController
def search
#stock = Stock.new_from_lookup(params[:stock])
render json: #stock
end
end
View
<h1>My portfolio</h1>
<h3>Search for stocks</h3>
<div id="stock-lookup">
<%= form_tag search_stocks_path, method: :get, id: "stock-lookup-form" do %>
<div class="form-group row no-padding text-center col-md-12">
<div class="col-md-10">
<%= text_field_tag :stock, params[:stock], placeholder: "Stock ticker symbol", autofocus: true, class: "form-control search-box input-lg" %>
</div>
<div class="col-md-2">
<%= button_tag(type: :submit, class: "btn btn-lg btn-success") do %>
<i class="fa fa-search"></i> Look up a stock
<% end %>
</div>
</div>
<% end %>
</div>
Gemfile
source 'https://rubygems.org'
git_source(:github) do |repo_name|
repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
"https://github.com/#{repo_name}.git"
end
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 5.1.4'
gem 'devise'
gem 'twitter-bootstrap-rails'
gem 'jquery-rails'
gem 'devise-bootstrap-views'
gem 'stock_quote'
# Use Puma as the app server
gem 'puma', '~> 3.7'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 5.0'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# See https://github.com/rails/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
# Use CoffeeScript for .coffee assets and views
gem 'coffee-rails', '~> 4.2'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 3.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
group :development, :test do
gem 'sqlite3'
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '~> 2.13'
gem 'selenium-webdriver'
end
group :development do
# Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :production do
gem 'pg'
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
Any help is appreciated!
It looks to me that you need a parameter called "stock". In line 4
#stock = Stock.new_from_lookup(params[:stock])
It is looking for a URL parameter called "stock" which is a ticker symbol. Try something like:
localhost:3000/search_stocks?stock=goog
That will create a stock param and give it something to look up. You could also put in some code to handle the case where the param is nil.
def search
if params[:stock]
#stock = Stock.new_from_lookup(params[:stock])
else
# you should have some directions here for what happens if there is no stock param given.
#stock = nil
end
render json: #stock
end
Probably even better to do that in the model, now that I think of it:
def self.new_from_lookup(ticker_symbol)
if ticker_symbol
looked_up_stock = StockQuote::Stock.quote(ticker_symbol)
else
# something here for a missing stock param
looked_up_stock = Stock.first
end
new(name: looked_up_stock.name, ticker: looked_up_stock.symbol, last_price: looked_up_stock.l)
end
I hope this helps!
You can try the following because most of the time gem functionality updating that's why not working on implementing this code. I've recently worked this type of project write code the below which is tested.
Model
def self.find_by_ticker(ticker_symbol)
where(ticker: ticker_symbol).first
end
def self.new_from_lookup(ticker_symbol)
begin
looked_up_stock = StockQuote::Stock.quote(ticker_symbol)
price = strip_commas(looked_up_stock.l)
new(name: looked_up_stock.name, ticker: looked_up_stock.symbol, last_price: price)
rescue Exception => e
return nil
end
end
def self.strip_commas(number)
number.gsub(",", "")
end
Hope to help
Gem file :
source 'https://rubygems.org'
ruby "2.3.1"
gem 'rails', '4.2.3'
gem 'bootstrap-sass', '~> 3.3.6'
gem 'font-awesome-rails', '4.3.0.0'
gem 'sass-rails', '~> 5.0.4'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'turbolinks'
gem 'jbuilder', '~> 1.2'
group :doc do
gem 'sdoc', require: false
end
group :development, :test do
gem 'byebug', platform: :mri
gem 'sqlite3'
end
group :development do
gem 'web-console'
gem 'listen', '~> 3.0.5'
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
end
group :production do
gem 'unicorn'
gem 'pg'
gem 'rails_12factor'
end
gem 'simple_form', '~> 3.2', '>= 3.2.1'
gem 'haml', '~> 4.0', '>= 4.0.7'
Controller: (rails g controller Academy/Student)
class Academy::StudentController < ApplicationController
before_action :find_course, only: [:show, :edit, :update, :destroy]
def index
#academy_students = Academy::Student.all.order("created_at DESC")
# render json: Academy::Student.all
end
def new
#academy_student = Academy::Student.new
end
def show
# list = Academy::Student.find(params[:id])
# render json: list
end
def create
if #academy_student = Academy::Student.create(academy_student_params)
redirect_to #academy_student
else
render :new
end
end
def edit
end
def update
if #academy_student.update(academy_student_params)
redirect_to #academy_student
else
render :edit
end
end
def destroy
#academy_student.destroy
redirect_to #academy_student
end
private
def find_course
#academy_student = Academy::Student.find(params[:id])
end
def academy_student_params
params.require(:academy_student).permit(:student_code, :student_name, :student_gender, :student_email, :student_phone, :student_address, :student_degree, :student_grade)
end
end
routes :
namespace :academy do
resources :student
end
academy_student_index GET /academy/student(.:format) academy/student#index
POST /academy/student(.:format) academy/student#create
new_academy_student GET /academy/student/new(.:format) academy/student#new
edit_academy_student GET /academy/student/:id/edit(.:format) academy/student#edit
academy_student GET /academy/student/:id(.:format) academy/student#show
PATCH /academy/student/:id(.:format) academy/student#update
PUT /academy/student/:id(.:format) academy/student#update
DELETE /academy/student/:id(.:format) academy/student#destroy
_form.html.haml
= simple_form_for(#academy_student, html: { class: 'form-horizontal'}) do |f|
= f.error_notification
.form-inputs
= f.input :student_code, label: 'Student Code:'
= f.input :student_name, label: 'Student Name:'
= f.input :student_gender, label: 'Student Gender:'
= f.input :student_email, label: 'Student Email:'
= f.input :student_phone, label: 'Student Phone:'
= f.input :student_address, label: 'Student Address:'
= f.input :student_degree, label: 'Student Degree:'
= f.input :student_grade, label: 'Student Grade:'
= f.button :submit
Error:
NoMethodError in Academy::Student#new (when i try to run : http://localhost:3000/academy/student/new)
undefined method `academy_students_path' for #<#:0x007fd586ab8278> Did you mean? academy_student_path
academy_student_index_path
academy_student_url
academy_courses_path
Note:
This controller & model are generate without scaffold (when i use scaffold, its work)
Its working with update.
According to the naming conventions, controller name should be plural.
Which in your case is students_controller
rename student_controller.rb to students_controller.rb change your controller class name to
class Academy::StudentsController < ApplicationController
# ...
end
also, make changes in routes
routes.rb
resources :students
which will give you
academy_students GET /academy/students(.:format) academy/students#index
POST /academy/students(.:format) academy/students#create
new_academy_students GET /academy/students/new(.:format) academy/students#new
edit_academy_students GET /academy/students/:id/edit(.:format) academy/students#edit
academy_students GET /academy/students/:id(.:format) academy/students#show
PATCH /academy/students/:id(.:format) academy/students#update
PUT /academy/students/:id(.:format) academy/students#update
DELETE /academy/students/:id(.:format) academy/students#destroy
You are trying get academy/student#index
In your given routes details you should see there is no name of academy_students_path And for the academy/student#index you have academy_student_index_path.
Use academy_student_index_path instead academy_students_path
I am having an issue with Serializers in Rails. I'm trying to render custom json for my models. To do so, I added active_model_serializer to my Gemfile and wrote some Serializers. The problem is that when I'm rendering JSON, it is rendering my whole object instead of calling the serializer.
Here is my code
User Serializer
class UserSerializer < ActiveModel::Serializer
attributes :id, :email
end
User Model
# Class model for users
class User < ActiveRecord::Base
...
end
Response Model
class Response
attr_accessor :status, :data
end
Show Method
def send_response(*args)
r = Response.new
r.status = args[0] # This is the HTTP code
r.data = args[1] # This is a user object
render json: r, status: r.status
end
Gemfile
source 'https://rubygems.org'
gem 'rails', '4.2.0'
gem 'pg'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'devise'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'omniauth'
gem 'active_model_serializers'
group :development, :test do
gem 'byebug'
gem 'web-console', '~> 2.0'
gem 'spring'
end
You would have to create a serializer for Response as well. When you pass a plain object to render json: it will just try to call .to_json on the object. It has no idea that serializers even exist for nested objects.
class ResponseSerializer < ActiveModel::Serializer
attributes :title, :body
belongs_to :user
end
--
def send_response(*args)
r = Response.new
r.status = args[0] # This is the HTTP code
r.data = args[1] # This is a user object
render json: r, status: r.status
end
I have a bookmarks resource and have mapped it to serve json by default under my api namespace like so in my routes.rb:
namespace :api, defaults: {format: 'json'} do
resources :bookmarks
get ':username', to: 'users#index'
get ':username/bookmarks/:id', to: 'users#show'
end
I have a Api::UsersController controller and a supporting BookmarkSerializer that works just fine on an individual bookmark resource like http://localhost:3000/api/emma_carter/bookmarks/87
But when I try to hit http://localhost:3000/api/emma_carter which is supposed to serve all bookmarks owned by the user, I get all different kinds of errors. Here is my Api::UsersController
module Api
class UsersController < ApplicationController
respond_to :json
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
render json: bookmarks
end
def show
user = User.find_by(username: params[:username])
bookmark = user.bookmarks.find_by(params[:id])
render json: bookmark
end
end
end
The show method works but the index method gives me ArgumentError in Api::UsersController#index
wrong number of arguments (1 for 0)
UPDATE: Full stack trace here: https://gist.github.com/amite/b79fc42bfd73de5a07bd
screenshot
Here is the serializer:
class BookmarkSerializer < ActiveModel::Serializer
attributes :id, :url, :title, :domain, :notes, :image, :created, :username
belongs_to :user
def created
object.created_at
end
def username
user.username
end
end
Looking at other solutions on stack overflow, I have also tried other versions of my index method:
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
bookmarks.map { |bookmark| ::BookmarkSerializer.new(bookmark)}.to_json #updated line
end
This gives me the error:
Missing template api/users/index, application/index with {:locale=>[:en], :formats=>[:json], :variants=>[], :handlers=>[:erb, :builder, :raw, :ruby, :coffee, :jbuilder]}.
Next the last version of my index method looks like this:
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
ActiveModel::ArraySerializer.new(bookmarks, each_serializer: ::BookmarkSerializer).to_json
end
This gives me the error uninitialized constant ActiveModel::ArraySerializer
What am I doing wrong? I am using rails 4.1.5 and the github version of the active_model_serializers
gem.
gem 'active_model_serializers', github: 'rails-api/active_model_serializers'
UPDATE: Since I am trying to output a collection of bookmarks I also tried using a separate serializer
BookmarksSerializer but I am getting the same error: ArgumentError in Api::UsersController#index
wrong number of arguments (1 for 0)
UPDATE2: Here is a version of the index method that kinda works in the sense that it renders the resource collection in json format:
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
respond_with bookmarks.to_json
end
But this still does not use the BookmarksSerializer
class BookmarksSerializer < ActiveModel::Serializer
attributes :id, :title
end
It just outputs the default hash
Full Gemfile
source 'https://rubygems.org'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.1.5'
# Use mysql as the database for Active Record
gem 'mysql2'
# Use SCSS for stylesheets
gem 'sass-rails', '~> 4.0.3'
# Use Uglifier as compressor for JavaScript assets
gem 'uglifier', '>= 1.3.0'
# Use CoffeeScript for .js.coffee assets and views
gem 'coffee-rails', '~> 4.0.0'
# See https://github.com/sstephenson/execjs#readme for more supported runtimes
# gem 'therubyracer', platforms: :ruby
gem 'bourbon'
gem 'neat'
gem 'bitters'
gem 'refills'
gem 'wisper'
gem 'rails-ioc'
gem 'reform'
gem 'cells'
gem "pundit"
gem 'active_model_serializers', github: 'rails-api/active_model_serializers'
gem "font-awesome-rails"
gem 'simple_form'
# Use jquery as the JavaScript library
gem 'jquery-rails'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
group :development, :test do
# Call 'debugger' anywhere in the code to stop execution and get a debugger console
gem 'byebug'
gem 'ffaker'
gem 'pry-rails'
gem 'capybara'
gem 'factory_girl_rails'
gem 'database_cleaner'
gem 'capybara-webkit'
gem 'rspec-cells'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-commands-rspec'
gem 'rspec-rails'
gem 'guard-rspec'
gem 'rb-fsevent' if `uname` =~ /Darwin/
end
# Use ActiveModel has_secure_password
gem 'bcrypt', '~> 3.1.7'
# Use unicorn as the app server
# gem 'unicorn'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
# Use debugger
# gem 'debugger', group: [:development, :test]
turns out I need to include v0.9 of the gem
gem 'active_model_serializers', github: 'rails-api/active_model_serializers', branch: '0-9-stable'
you could try change:
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
bookmarks.map { |bookmark| ::BookmarkSerializer.new(bookmark)}.to_json #updated line
end
to:
def index
user = User.find_by(username: params[:username])
bookmarks = user.bookmarks
render json: bookmarks.map { |bookmark| ::BookmarkSerializer.new(bookmark)}
end