I have the following controller - recipes_controller.rb:
class RecipesController < ApplicationController
def list
#search_term = params[:looking_for] || 'chicken'
#courses = Recipe.for(#search_term)
end
end
The following model: recipes.rb:
require 'httparty'
class Recipe
include HTTParty
default_options.update(verify: false)
base_uri 'http://food2fork.com/api/search'
default_params key: ENV['FOOD2FORK_KEY']
format :json
def self.for term
get("", query: { q: term})["recipes"]
end
end
& the following view- list.html.erb:
<h1>Searching for - <%= #search_term %></h1>
<table border="1">
<tr>
<th>Image</th>
<th>Publisher</th>
<th>Title</th>
</tr>
<% #courses.each do |course| %>
<tr class=<%= cycle('even', 'odd') %>>
<td><%= image_tag(course["image_url"])%></td>
<td><%= course["publisher"] %></td>
<td><%= course["title"] %></td>
</tr>
<% end %>
</table>
When I do http://localhost:3000/recipes/list, it gives me the following error:
814: unexpected token at 'FORBIDDEN'
Application Trace | Framework Trace | Full Trace
app/models/recipe.rb:13:in for'
app/controllers/recipes_controller.rb:4:inlist'
the json is of the following format:
{"count": 1, "recipes": [{"publisher": "Tasty Kitchen", "f2f_url": "http://food2fork.com/view/459b3d", "title": "End the Search Chocolate Chip Cookies", "source_url": "http://tastykitchen.com/recipes/desserts/end-the-search-chocolate-chip-cookiese280a6/", "recipe_id": "459b3d", "image_url": "http://static.food2fork.com/cookie2410x307a33e.jpg", "social_rank": 34.80777735743579, "publisher_url": "http://tastykitchen.com"}]}
Please let me know what to do.
Ok, so I just went and signed up for a food2fork.com api key and read their documentation, there are a couple of issues with your code.
If you take a look under the search api documentation, you'll see the explanation of the query parameters:
q: (optional) Search Query (Ingredients should be separated by commas). If this is omitted top rated recipes will be returned.
What that means is that you're sending a request for a recipe with ingredients of "keyword" with every request you're making. Obviously there are no recipes with an ingredient named keyword.
If you look in the same section, there seems to be no query parameters for "fields," so you shouldn't be adding them. It doesn't hurt, but why have code that does nothing in your class?
You're likely wanting to get your key from the environment rather than hardcoding it into your app.
All that being said, I think you want something like this as your Recipe class:
require 'httparty'
class Recipe
include HTTParty
default_options.update(verify: false)
base_uri 'http://food2fork.com/api/search'
default_params key: ENV['FOOD2FORK_KEY']
format :json
def self.for term
get("", query: { q: term })["recipes"]
end
end
Related
I have a jsonb attribute in my model, called config_params.
config_params: {key_a: 1, key_b: 100}
I need to edit it in rails_admin, but I need to edit each key as a different field.
key_a a field and key_b another, and if exists.
The question is because each config_param can hold different keys, it makes as if I need to know the object before building the fields and I can't only build virtual fields. It needs to do dynamic.
I created dynamic attributes on the model by singleton, it works fine! but I can't access the object in the building time of fields to get what config_params keys there are to build the fields.
I resolced it with singleton methods and one partial.
Into model I added calbacks to load attributes in run time:
after_initialize :load_configs
after_find :load_configs
And the load_configs method
def load_configs
config_params&.keys&.each do |param|
define_singleton_method(param) do
config_params[param]
end
define_singleton_method("#{param}=") do |value|
config_params[param] = value
end
end
end
OK! Now you have a jsonb field and into this there are many keys end each key has an attribute on instance method.
Exemple:
instance.config_params = {'key_a': 1, key_b: 2}
instance.save
instance.reload.key_a
1
instance.key_a = 10
instance.key_a
10
instance.key_b
2
OK! Model ok!
Now you need load it into admin, ok?
let´s go!
into a file that you config the rails_admin to your model put it
edit do
group 'Configurations' do
field :config_params do
partial 'config_params_partial'
end
end
end
Now you need create the partial file into de folder
app/views/rails_admin/main/_config_params_partial.html.erb
The partial code is like it
<% #object.config_params&.each do |key, value| %>
<%= form.fields_for :config_params, #object.config_params[key] do |config_params_fields| %>
<div class="field controls">
<%= config_params_fields.label #object.type.config_params[key]['label'] %>
<br/>
<%= config_params_fields.text_field key,
:value => value,
type: #object.type.config_params[key]['kind'],
disabled: #object.type.config_params[key]['editable'] == false,
class: 'form-control' %>
<% if #object.type.config_params[key]['required'] == true %>
<span class="help-block">Obrigatório. </span>
<% end %>
</div>
<% end %>
<% end %>
OBS: I created a model called type that control each meta-info about my fields, but you can create it locked, only fill field properties.
Restart server and be happy!
New to Ruby on Rails. I want to update the new.html.erb view to display search_result from album_controller.rb, after the search parameter is submitted. Can I create a hidden element and later display it through from the controller? search_results is an array of string arrays.
My goal to display a list of possible albums to choose from (obtained from the http get request), and then let the user choose which search result to carry on with.
new.html.erb
<h4>Search for Album</h4>
<%= form_with(url: "/albums/search", method: "get") do %>
<%= label_tag(:q, "Search for: ") %>
<%= text_field_tag(:q) %>
<%= submit_tag("Search") %>
<% end %>
album_controller.rb
require 'httparty'
require 'json'
class AlbumsController < ApplicationController
def new
puts "the new method was just called"
end
def search
puts "inside search method"
#search_param = params[:q]
puts #search_param
if params[:q] # if not null
#perform artist and album search
search_result = http_request(#search_param)
end
end
def http_request(search_input)
# search for album
website = "http://ws.audioscrobbler.com/2.0/?"
search_album = "method=album.search&album=" + search_input + "&"
api_key = "api_key=&"
format = "format=json&"
limit = "limit=15"
url = website + search_album + api_key + format + limit
response = HTTParty.get(url)
pretty_json = JSON.pretty_generate(response.parsed_response)
obj = JSON.parse(pretty_json)
returnArray = []
for result in obj["results"]["albummatches"]["album"]
tempArray = [result["name"], result["artist"], result["image"][3]["#text"]]
returnArray.append(tempArray)
end
return returnArray
end
end
routes.rb
Rails.application.routes.draw do
get 'welcome/index'
post "albums/new" #to later post to album db
get "albums/search" #search route in new.html.erb
resources :albums
root 'welcome#index'
end
In Rails you want to build skinny controllers that contain the minimum of code and the only public methods should be the actions that correspond to your routes. This is because controllers are notoriously hard to test and bloat becomes a problem real fast.
HTTP calls, batch processing and other such tasks do not belong in your controller. Especially not if they touch the application boundary. Instead you want to create a client object that handles the HTTP call and models that encapsulate the data and normalize it for your application.
So lets start with the HTTP call:
# app/clients/audio_scobbler_client.rb
class AudioScrobblerClient
include Httparty
format :json
base_uri "http://ws.audioscrobbler.com/2.0/"
def initialize(api_key:)
#base_opts = {
api_key: api_key,
format: "json" # may be redundant
}
end
def album_search(query, limit: 15)
self.class.get(
#base_opts.reverse_merge(
method: 'album.search',
limit: limit
)
)
end
end
This gives you an object you can test separately from your controller and removes the mess of constructing query strings with string concatenation (never do this). It returns the JSON from performing the HTTP request and nothing more.
Then create a model that represents the search results in your application. Remember that persistence is not the only role of models in MVC.
# app/models/album.rb
class Album
include ActiveModel::Model
include ActiveModel::Attributes
attr :artist, String
attr :name, String
attr :image, String
end
Now lets throw one more object into the the mix - a service object that does the API call and normalizes the values:
# app/services/audio_scrobbler_search.rb
class AudioScrobblerSearch
def perform(query, **options)
api_key = ENV["AUDIOSCROBBER_API_KEY"] # or use the encrypted secrets.
json = AudioScrobblerClient.new(api_key: api_key).album_search(query, options)
json.dig("results", "albummatches", "album").map do |result|
# I have no idea what api response looks
# like but I have no doubt that you can figure this part out
Album.new(
artist: result["name"],
name: result["name"],
image: result["image"]
)
end
end
end
Then lets get rid of all the bloat in the controller:
class AlbumsController < ApplicationController
# you don't really need the new action at all since a search form can just loop back on itself
# GET /albums/search?q=believe
def search
#search_query = params[:q]
if #search_query
#albums = AudioScrobblerSearch.perform(query)
end
end
end
And list the albums in the view:
<h4>Search for Album</h4>
<%= form_with(url: "/albums/search", method: "get") do %>
<%= f.label(:q, "Search for: ") %>
<%= f.text_field(:q, value: #search_query) %>
<%= f.submit("Search") %>
<% end %>
<% if #albums %>
<table>
<thead>
<tr>
<th>Image</th>
<th>Artist</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<% #albums.each do |album| %>
<tr>
<td><%= tag.img src: album.image, alt: "Cover art for #{album.name}" %></td>
<td><%= album.artist %></td>
<td><%= album.name %></td>
</tr>
<% end %>
</tbody>
</table>
<% elsif #search_query.present? %>
<p>No results to display :(</p>
<% end %>
This must be a common need but I can't seem to find a definitive answer on the most rubyesque way. I need to create a fairly complex algorithm to dynamically calculate course grades in a rails 4.1 app.
Specifically, I have a model, "course", and whenever an instance of it is displayed in the view, I want to dynamically calculate the current grade (a decimal value, calculated from many course.field values) and display it as a letter value using a switch/case. My assumption was that I could do this in the controller (but it almost seems like it's complex enough to warrant it's own -- module? In C++ I would create a class). At the same time, since it is created dynamically, it seemed like bad form to create a current_grade field for it in the model, so it's not one I can pass back and forth as one of the allowable params (that I know of-- can one pass a variable in the params that is not represented in the db?).
In my initial research I see suggestions of hidden_field_tags and helper_methods and all_helpers and modules and global modules and more. Under time pressure, I dread beginning down the wrong path. Which is the better approach? Or a good high level doc for reference?
As an example, here is one view in which I would like to calculate current grade, compare it to desired grade, and display accordingly.
# index.html.erb
<% #courses.each do |course| %>
<li>
<%= my_algorithm_to_calculate_curr_grade(many course.fields used to caluculate)
<= course.desired_grade ? "set li to <Color: red>" : "set li to <Color: green>" %>
<%= course.course_name %>
Current Calculation: <%= display_results_of_previous_calculation %>
(Goal: <%= course.desired_grade %>)
<%= link_to 'Show', course %>
<%= link_to 'Edit', edit_course_path(course) %>
<%= link_to 'Drop Course Without Penalty', course, method: :delete, data: { confirm: 'Are you sure?' } %>
</li>
<% end %>
It's hard to tell from your question if course.fields are attributes of Course or different model(s). If all the fields are Course attributes, I would put it as an instance method on Course.
class Course < ActiveRecord::Base
def calculated_grade
# fun algorithm
end
end
If course.fields need to be loaded from the database, I'd probably go with a Plain Old Ruby Object (PORO), maybe call it CourseGradeCalculator (put it in app/models, why not? It's business logic)
class CourseGradeCalculator
attr_reader :course, :fields, :grade
def initialize(course, fields)
#course = course
#fields = fields
#grade = calculate_grade
end
private
def calculate_grade
# fun algorithm
end
end
# controller
#course = Course.preload(:fields).find(params[:id]
# view
CourseGradeCalculator.new(#course, #course.fields)
I have HTML code in my Rails application that looks like this:
<td><%= MyObj.find_by_id(#my_obj.some_id).name %></td>
And this shows up fine. However, the field some_id is not always present in the database, so I'd like to display NA rather than MyObj's name in those cases.
So here's what I mean in pseudocode:
<td><%= IF #my_obj.some_id is NIL THEN "NA" ELSE MyObj.find_by_id(#my_obj.some_id).name %></td>
How can I do this?
You can use the following snippet:
<td><%= #my_obj.some_id.present? ? MyObj.find(#my_obj.some_id).name : 'NA' %></td>
However, you're exposing yourself to some pretty big issues if you're finding records in your view code like this. Your controller should handle that kind of logic.
You should not be hitting the database in your view code, leave that to your controllers. Try loading the object in your controller instead:
# use this code to your controller
#view_obj = MyObj.find_by_id(#my_obj.some_id)
You can then use the try method to set the name, which will return nil instead of raising the usual NoMethodError if the object is nil:
<%= #view_obj.try(:name) || "NA" %>
<%= !MyObj.find_by_id(#my_obj.some_id).name.blank? ? MyObj.find_by_id(#my_obj.some_id).name : "NA" %>
This helped for display on an individual 'items' show page
Accessing an attribute of a linked model in Rails
However I'm having trouble doing the same for an 'all items table'
...
<% #items.each do |item| %>
...
<td><%= item.room.name %></td>
...
Clearly where one room has many items.
only this works:
<td><%= item.room_id %></td>
I can't seem to use it there, gives:
undefined method `name' for nil:NilClass
Have a look at Rails' Delegate module:
class Item < ActiveRecord::Base
# ...
delegate :name, :to => :room, :allow_nil => true, :prefix => :room
end
This will add the instance method room_name to Item, which will fail more gracefully (returning nil if there is no room, instead of the NilClass error).