Rails Access data from a different Model within a Controller - ruby-on-rails

UPDATE: Problem solved, thanks to Sebastian and Gabriel for the helpful pointers.
The relevant changes to my code are as follows:
app/controllers/pomodoro_cycles_controller.rb
def pomodoro_collections
{
pomodoro_collection_0: Pomodoro.offset(0).first(100),
pomodoro_collection_1: Pomodoro.offset(100).first(100)
}
end
app/views/pomodoro_cycles/show.html.erb
<% #pomodoros_collections.each do |pomodoros_collection_hash| %>
<h2><%= pomodoros_collection_hash[0] %></h2>
<% pomodoros_collection_hash[1].each do |pomodoro| %>
<p>
<%= pomodoro.id %>
<%= pomodoro.color %>
</p>
<% end %>
<% end %>
NOTA BENE:
The #first method in ActiveRecord returns an Array, so the keys in my original Hash were nested Arrays. Instead, the following was sufficient to return an Array of Pomodoro objects:
Pomodoro.offset(0).first(100)
DESCRIPTION OF ORIGINAL PROBLEM
Rails 5, PostgreSQL
PROBLEM: I cannot access Pomodoro.all from within PomodoroCycleController
I have two scaffolds: Pomodoro and PomodoroCycle, and I want to access the full list of Pomodoros within the PomdoroCycle controller.
The following code is kept simple, in order to make as clear as possible what I'm trying to do. If I can do these things, then I'll be able to do much more, but one step at a time.
Regarding the db migration files, I have already run bundle exec rails db:migrate
I want to display a full list of Pomodoros in the PomodoroCycle Show View (later to be displayed in Index), but I don't know what is missing.
From app/controllers/pomodoro_cycles_controller.rb
def show
#pomodoros_collections = pomodoro_collections
end
def pomodoro_collections
{
pomodoro_collection_0 => [Pomodoro.offset(0).first(100)],
pomodoro_collection_1 => [Pomodoro.offset(100).first(100)]
}
end
From app/views/pomodoro_cycles/show.html.erb
<% #pomodoros_collections.each do |collection| %>
<p><%= collection %></p>
<% end %>
However, this displays nothing in the browser.
app/models/pomodoro_cycle.rb
class PomodoroCycle < ApplicationRecord
has_many :pomodoros
end
app/models/pomodoro.rb
class Pomodoro < ApplicationRecord
belongs_to :pomodoro_cycle
end
Updated db/migrate/20180103032759_create_pomodoro_cycles.rb:
class CreatePomodoroCycles < ActiveRecord::Migration[5.1]
def change
create_table :pomodoro_cycles do |t|
t.string :activity
t.integer :iteration
t.integer :matrix_side_length
t.datetime :created_at
t.datetime :completed_at
t.string :category_labels, array:true, default: []
t.string :category_colors, array:true, default: []
t.string :username
t.timestamps
end
create table :pomodoros do |t|
t.belongs_to :pomodoro_cycle, index: true
t.datetime :completed_at
t.timestamps
end
add_index :pomodoros, :pomodoro_cycle_id
end
end
Untouched db/migrate/20180103054425_create_pomodoros.rb
class CreatePomodoros < ActiveRecord::Migration[5.1]
def change
create_table :pomodoros do |t|
t.boolean :status
t.string :category
t.string :color
t.datetime :completed_at
t.string :username
t.timestamps
end
end
end

First of all, as #SebastianPalma pointed out in the comments, the syntax is wrong
def pomodoro_collections
{
pomodoro_collection_0 => [Pomodoro.offset(0).first(100)],
pomodoro_collection_1 => [Pomodoro.offset(100).first(100)]
}
end
should be:
def pomodoro_collections
{
pomodoro_collection_0: [Pomodoro.offset(0).first(100)],
pomodoro_collection_1: [Pomodoro.offset(100).first(100)]
}
end
Make the keys in the hash symbols
Then to display each Pomodoro put something like:
<% #pomodoros_collections.each do |pomodoros_collection_hash| %>
<h2><%= pomodoros_collection_hash[0] %></h2>
<% pomodoros_collection_hash[1].each do |pomodoro| %>
<p><%= pomodoro.id %></p> #Or the attribute you want to display
<% end %>
<% end %>
Hope this help

Related

Why am I seeing a hash rendered to the DOM?

I am building a Portfolio website that has a simple view file for projects I have worked on. On my "work" view I render a collection of "technologies" that I used to build a particular product. This is working great, and each technology renders just fine. However, right below the that renders the technologies the plain hash is also being rendered, and I cannot figure out why.
I am following a tutorial for this and I have double checked that my code is the same as the instructor's.
Work View
<%= image_tag #work_item.main_image unless #work_item.main_image.nil? %>
<h1>Title: <%= #work_item.title %></h1>
<em><%= #work_item.subtitle %></em>
<p><%= #work_item.body %></p>
<h2>Technologies Used:</h2>
<%= #work_item.technologies.each do |t| %>
<p><%= t.name %></p>
<% end %>
Schema
create_table "technologies", force: :cascade do |t|
t.string "name"
t.bigint "work_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["work_id"], name: "index_technologies_on_work_id"
end
controller method being used via def show via a before_action
def set_work
#work_item = Work.find(params[:id])
end
Technology Model
class Technology < ApplicationRecord
belongs_to :work
end
Work Model
class Work < ApplicationRecord
has_many :technologies
accepts_nested_attributes_for :technologies,
reject_if: lambda { |attrs| attrs['name'].blank? }
include Placeholder
validates_presence_of :title, :body, :main_image, :thumb_image
def self.react
where(subtitle: "React")
end
scope :ruby_on_rails, -> { where(subtitle: "Ruby on Rails") }
after_initialize :set_defaults
def set_defaults
self.main_image ||= Placeholder.image_generator(height: 600, width: 400)
self.thumb_image ||= Placeholder.image_generator(height: 350, width: 200)
end
end
Here is a screenshot of what I'm seeing
https://imgur.com/a/2T3SRZv
Because the = in
<%= #work_item.technologies.each do |t| %>
indicates that you want #work_item.technologies to be output to the view.
Instead, use
<% #work_item.technologies.each do |t| %>
BTW, that's not a hash, it's an enumerable.

How do I select the first image based on id and display in a (show) view?

In my application, there are user submitted reviews. Each review has many photos. What I need is to loop over the reviews that belong to a user and show the first photo in each review.
I appreciate your help :)
This works in console and I am able to return the correct photo. In review 1 there are 3 photos and this correctly returned the first one.
Photo.where(:review_id => 1).pluck(:file_name).first
Now I'm trying to display them in the users/show.html.erb file as such and it doesn't work.
<% #user.reviews.each do |review| %>
<%= image_tag review.firstphoto.url %>
<% end %>
I also tried defining a method to obtain the first photo in reviews_controller.rb
def firstphoto
#photo = Photo.where(:review_id => #review_id).pluck(:file_name).first
end
Here are the associations
class Review < ActiveRecord::Base
belongs_to :brand
belongs_to :user
has_many :photos
class Photo < ActiveRecord::Base
belongs_to :review
class User < ActiveRecord::Base
has_many :reviews
The photos are stored in S3 and use this schema to store in database.
create_table "photos", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.text "file_name"
t.integer "review_id"
To illustrate, the photos are correctly displayed in reviews/show.html.erb correctly with this code.
<% #review.photos.each do |photo| %>
<%= image_tag photo.file_name.url %>
<% end %>
In users_controller.rb show action
#reviews = current_user.reviews
Then in users/show.html.erb file
<% #reviews.each do |review| %>
<%= image_tag review.first_photo %>
<% end %>
Now create a instance method in review.rb model be following code.
def first_photo
photos.first.file_name.url if photos.first.present?
end
This will do what you want. Let me know If you are still facing this issue.

Importing CSV data into Rails app, using something other then the association "id"

I am trying to import a few CSV files into my rails app. I learned and managed to import tables into Models without association.
Now i have managed to import the data into a table that has associations, but only by entering the actual "id" number on the CSV column. Although functional, this isn't really an option because i have many tables with thousands of IDs.
My main goal is to be able to use the column in the CSV and type in the actual value (that exists in the other model it is associated with), instead of the id number.
I have a Country model and a Ports model. The Ports model is associated with country_id
Port Model
class Port < ApplicationRecord
def self.import(file)
#code
CSV.foreach(file.path, headers: true) do |row|
port = find_by_id(row["id"])
Port.create! row.to_hash
end
end
belongs_to :shipment_type
belongs_to :country
has_many :origins, :class_name => 'Rate'
has_many :destinations, :class_name => 'Rate'
end
Country Model
class Country < ApplicationRecord
def self.import(file)
#code
CSV.foreach(file.path, headers: true) do |row|
Country.create! row.to_hash
end
end
has_many :ports, dependent: :destroy
end
schema.db
create_table "ports", force: :cascade do |t|
t.string "name"
t.string "port_code"
t.integer "shipment_type_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "country_id"
t.index ["country_id"], name: "index_ports_on_country_id", using: :btree
t.index ["shipment_type_id"], name: "index_ports_on_shipment_type_id", using: :btree
end
create_table "countries", force: :cascade do |t|
t.string "name"
t.string "country_code"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "shipment_types", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
The associations are working because i am able to manually add them view my forms i create just fine.
<%= form_for(port) do |f| %>
<% if port.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(port.errors.count, "error") %> prohibited this port from being saved:</h2>
<ul>
<% port.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :port_code %>
<%= f.text_field :port_code %>
</div>
<div class="field">
<%= f.label :shipment_type_id %>
<%= f.collection_select :shipment_type_id, ShipmentType.all, :id, :name %>
</div>
<div class="field">
<%= f.label :country_code %>
<%= f.collection_select :country_id, Country.all, :id, :country_code %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Any guidance or help would be greatly appreciated. I have been going in circles with this for days now.
ADDING SAMPLE TABLE FROM CSV FILE.
A shipment_type is a ruby object, you want to send a string.
If you are needing to import relationships, add methods on the Port model like so
class Port < ApplicationRecord
def shipment_type_name
shipment_type.try(:name)
end
def shipment_type_name=(name)
self.shipment_type = ShipmentType.where(:name => name).first_or_create
end
def country_country_code
country.try(:country_code)
end
def country_country_code=(code)
self.country = Country.where(:country_code => code).first
end
end
Then in the CSV you'd send a shipment_type_name and country_country_code attributes.
You would do something similar to other relationships.
You may want to use this gem for importing CSV:
https://github.com/michaelnera/active_record_importer
It's easy to use.
Thank you everyone for the help. Below is what ended up working for me. The biggest issue i was getting was Origin and Destination. There is only one Port table, which includes a list of the Ports. Ports are used for both Origin and Destination.
class Rate < ApplicationRecord
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
rate = find_by_id(row["id"])
Rate.create! row.to_hash
end
end
belongs_to :origin, :class_name => 'Port'
belongs_to :destination, :class_name => 'Port'
belongs_to :carrier
belongs_to :shipment_category
belongs_to :unit_of_measure
has_many :additional_items
# associatiing Origin and Destination Port Code
def origin_port_code
origin.try(:port_code)
end
def origin_port_code=(port_code)
self.origin = Port.where(:port_code => port_code).first
end
def destination_port_code
destination.try(:port_code)
end
def destination_port_code=(port_code)
self.destination = Port.where(:port_code => port_code).first
end
# associating carrier name
def carrier_name
carrier_name.try(:name)
#code
end
def carrier_name=(name)
self.carrier = Carrier.where(:name => name).first
#code
end
# associating Shipment Category Name
def shipment_category_name
shipment_category.try(:name)
end
def shipment_category_name=(name)
self.shipment_category = ShipmentCategory.where(:name => name).first
end
# associating unit_of_measure name
def unit_of_measure_name
unit_of_measure.try(:name)
#code
end
def unit_of_measure_name=(name)
self.unit_of_measure = UnitOfMeasure.where(:name => name).first
#code
end
end

undefined method `image' for #<Array> when using group_by

Hi can anyone point me to the right direction? I´m trying to show images on views/pages/index.html.erb the images are uploaded on views/products/new.html.erbthrough the _form.html.erbpartial. Each product/picture then belongs to a category which I can select in the _navbar.html.erb and is then directed to the views/categories/show.html.erbto see pictures of each product in that category and so on.
That is all working fine
But now I want to display the last added picture in each category on the views/pages/index.html.erb and I´m always getting this error : undefined method 'image' for #<Array:0x007f8d1fb19ff0>
I´m pretty lost at the moment, and hopefully someone can guide me to the right path.
My code id like this:
pages_controller.rb
class PagesController < ApplicationController
def index
#products = Product.all.order(created_at: :desc).group_by(&:category_id)
end
def about
end
def location
end
def stockists
end
end
views/pages/index.html.erb
<% #products.each do |product| %>
<div class="col-lg-3 col-sm-6 col-xs-12 center-block " >
<%= image_tag product.image.url(:medium) %>
<p><%= product.name %></p>
<p><%= product.category.name %></p>
<% end %>
</div>
And then I have, the products.rb and category.rb
product.rb
class Product < ActiveRecord::Base
mount_uploader :image, ImageUploader
validates_presence_of :name, :price
validates_numericality_of :price
belongs_to :category
end
category.rb
class Category < ActiveRecord::Base
has_many :products
end
this as part of the schema.rb
create_table "products", force: :cascade do |t|
t.string "name"
t.string "description"
t.float "price"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "category_id", default: 1
end
add_index "products", ["category_id"], name: "index_products_on_category_id", using: :btree
and in the end there is this part
add_foreign_key "order_items", "orders", on_delete: :cascade
add_foreign_key "order_items", "products"
add_foreign_key "orders", "users", on_delete: :cascade
add_foreign_key "products", "categories"
end
You are using group_by in the controller, an enumerable method that returns a hash of Product arrays keyed by category_id.
#product = {
:category1 => [#<Product category_id=1>, #<Product category_id=1>, ...],
:category2 => [#<Product category_id=2>, #<Product category_id=2>, ...]
}
When you loop through #products in the view, you are looping through a hash where each iteration is passing an array.
The product variable does not contain a product, but an array of products.
<% #products.each do |product| %> # product is type Array!
<%= image_tag product.image.url(:medium) %> # Array.image throws an error!
<% end %>
You must create an outer loop to step through the hash.
<% #products.each do |category, products| %>
<% products.each do |product| %>
# do stuff
<% end %>
<% end %>

Count number of times a link is clicked

So i'm trying to record the number of times a link is clicked but can't get over the last hurdle.
I have the following so far:
config/routes.rb
resources :papers do
resources :articles do
resources :clicks
end
end
click.rb
class Click < ActiveRecord::Base
belongs_to :article, counter_cache: true
validates :ip_address, uniqueness: {scope: :article_id}
end
clicks_controller.rb
class ClicksController < ApplicationController
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
end
end
article.rb
class Article < ActiveRecord::Base
has_many :clicks
end
schema.rb
create_table "clicks", force: true do |t|
t.integer "article_id"
t.string "ip_address"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "articles", force: true do |t|
t.datetime "created_at"
t.datetime "updated_at"
t.text "title"
t.string "url"
t.integer "paper_id"
t.integer "clicks_count"
end
index.html.erb -- articles
<% #articles.each do |article| %>
<div class="articles col-md-4">
<%= link_to article.url, target: '_blank' do %>
<h4><%= article.title %></h4>
<h5><%= article.paper.name.upcase %></h5>
<h6><%= article.created_at.strftime("%d %B %y") %></h6>
<% end %>
Firstly, does this setup look correct, does anyone see where i may have gone wrong?
Secondly, what i don't know how to set up my view so that when the existing link is clicked the click is registered and the count goes up?
Thanks
Solved with the following.
clicks_controller.rb
Original:
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
end
end
Amended:
def create
#article = Article.find(params[:article_id])
#click = #article.clicks.new(ip_address: request.ip)
#click.save
redirect_to #article.url
end
end
index.html.erb -- articles
Original:
<%= link_to article.url, target: '_blank' do %>
Amended:
<%= link_to paper_article_views_path(article.id, article), method: :post, target: '_blank' do %>
Also, i edited the original question to include the routes.rb file.
In my opinion, you should do 2 things :
1) Set all the methods for "clicks" into a Model
For example, you can remove your ClicksController and add this :
class Article
def create_click(ip_address)
self.clicks.create({ :ip_address => ip_address })
end
end
A little note with this code : you have a uniqueness validation in your code. Indeed, when a click already exists for an article and an ip address, the create method will return false. Do not use create! instead, or it will raise an exception.
2) Add a filter :
You can simply add a filter in your ArticlesController. At each show, it will create a click instance for the viewed article
class ArticlesController
before_filter :create_click, :only => [ :show ]
def create_click
#article.create_click(ip_address)
end
end

Resources