I'm trying to build a Rails helper that will create a nested dropdown menu containing links where the top most is either "All" or the current param and the dropdown contains the other options excluding the current param if there is one.
For example, if I have no post_type param I would see:
<ul>
<li>All
<ul>
<li>Discussions</li>
<li>Snaps</li>
<li>Code</li>
<li>Links</li>
</ul>
</li>
</ul>
But if I had a post_type param of 'discussion' then I would see:
<ul>
<li>Discussions
<ul>
<li>All</li>
<li>Snaps</li>
<li>Code</li>
<li>Links</li>
</ul>
</li>
</ul>
In my view I have:
<ul class="filter-menu">
<li>
<%= current_post_type(params) %>
<ul class="filter-menu__drop-down">
<%= list_post_types(params) %>
</ul>
</li>
</ul>
In my Helper I have:
module PostsHelper
def post_types
#post_types = {
:all => {
:text => 'All post types',
:icon => 'icon-file-text2',
:post_type => nil}
}, {
:discussions => {
:text => 'Discussions',
:icon => 'icon-bubbles2',
:post_type => 'discussions'}
}, {
:snaps => {
:text => 'Snaps',
:icon => 'icon-images',
:post_type => 'snaps'}
}, {
:code => {
:text => 'Code',
:icon => 'icon-embed2',
:post_type => 'code'}
}, {
:links => {
:text => 'Links',
:icon => 'icon-link',
:post_type => 'links'}
}
end
def post_type_text(icon, text, drop_down = false)
raw('<i class="' + icon + '"></i> ' + text + (drop_down ? ' <span class="chevron">▾</span>' : ''))
end
def post_type_path(post_type)
posts_path(:filter => params[:filter], :time => params[:time], :post_type => post_type)
end
def current_post_type(params)
if params[:post_type].present? # todo: check matches above
post_type = params[:post_type].downcase
link_to post_type_text(post_types[post_type][:icon], post_types[post_type][:text], true), post_type_path(post_types[post_type][:post_type])
else
link_to post_type_text(post_types[:all][:icon], post_types[:all][:text], true), post_type_path(post_types[:all][:post_type])
end
end
def list_post_types(params)
post_types.each do |post_type| # todo: exclude current post_type
link_to post_type_text(post_types[post_type][:icon], post_types[post_type][:text]), post_type_path(post_types[post_type][:post_type])
end
end
end
How do I access the hash though? I get an error
no implicit conversion of Symbol into Integer
when doing post_types[:all].
I think it's because post_types is returning an array of hashes where as what I want is a hash of hashes accessible by key names.
I could access the :all via post_types[0][:all][:icon] but this won't work for my other hashes, as I want to access them via post_types[post_type][:icon] where post_type is the name of the post_type key I am trying to access.
First of all, declare a hash instead of an array:
#post_types ||= {
:all => {
:text => 'All post types',
:icon => 'icon-file-text2',
:post_type => nil},
:discussions => {
:text => 'Discussions',
:icon => 'icon-bubbles2',
:post_type => 'discussions'},
:snaps => {
:text => 'Snaps',
:icon => 'icon-images',
:post_type => 'snaps'},
:code => {
:text => 'Code',
:icon => 'icon-embed2',
:post_type => 'code'},
:links => {
:text => 'Links',
:icon => 'icon-link',
:post_type => 'links'}
}
The problem is here:
post_types.each do |post_type| # todo: exclude current post_type
link_to post_type_text(post_types[post_type] ...
You are already iterating over your hash, post_type here is an array, holding both key and value.
Use:
post_types.each do |k, v| # todo: exclude current post_type
link_to post_type_text(v[:icon] ...
To understand what’s going on:
post_types.each do |post_type| # todo: exclude current post_type
puts post_type.inspect
Also, sidenote: instantiate the instance variable only once:
def post_types
# ⇓⇓⇓ HERE
#post_types ||= {...}
This what I have ended up with in the end to solve my problem.
Massive thanks to mudasobwa whose answer I have accepted but I wanted to share what the final code looked like for anyone who comes across this.
module PostsHelper
def post_types
#post_types ||= {
:all => {
:text => 'All post types',
:icon => 'icon-file-text2',
:post_type => nil},
:discussions => {
:text => 'Discussions',
:icon => 'icon-bubbles2',
:post_type => 'discussions'},
:snaps => {
:text => 'Snaps',
:icon => 'icon-images',
:post_type => 'snaps'},
:code => {
:text => 'Code',
:icon => 'icon-embed2',
:post_type => 'code'},
:links => {
:text => 'Links',
:icon => 'icon-link',
:post_type => 'links'}
}
end
def post_type_text(icon, text, drop_down = false)
raw('<i class="' + icon + '"></i> ' + text + (drop_down ? ' <span class="chevron">▾</span>' : ''))
end
def post_type_path(post_type)
posts_path(:filter => params[:filter], :time => params[:time], :post_type => post_type)
end
def current_post_type(params)
post_type = params[:post_type].present? ? params[:post_type].downcase.to_sym : :all
if post_types.key?(post_type)
link_to post_type_text(post_types[post_type][:icon], post_types[post_type][:text], true), post_type_path(post_types[post_type][:post_type])
else
link_to post_type_text(post_types[:all][:icon], post_types[:all][:text], true), post_type_path(post_types[:all][:post_type])
end
end
def list_post_types(params)
html = ''
post_type = params[:post_type].present? ? params[:post_type].downcase.to_sym : :all
exclude_all = post_types.key?(post_type) ? false : true
post_types.each do |k, v|
if exclude_all && k == :all
else
html += "<li>#{link_to post_type_text(v[:icon], v[:text]), post_type_path(v[:post_type])}</li>" if k != post_type
end
end
html.html_safe
end
end
I'm using the search API, and now need to add the completion suggester, I'm using elasticsearch-rails gem.
When I search for an article, everything works
http://localhost:9200/articles/_search
"query": {
"multi_match": {
"query": "test",
"fields": [
"title", "tags", "content"
]
}
}
}
But since I've implemented the completion suggester I had to edit as_indexed_json to make it work, but now the search API doesn't work anymore, only the suggestions.
Here is my Article model:
def self.search(query)
__elasticsearch__.search(
{
query: {
multi_match: {
query: query,
fields: ['title', 'content', 'tags']
}
}
})
end
def self.suggest(query)
Article.__elasticsearch__.client.suggest(:index => Article.index_name, :body => {
:suggestions => {
:text => query,
:completion => {
:field => 'suggest'
}
}
})
end
def as_indexed_json(options={})
{
:name => self.title,
:suggest => {
:input => self.title,
:output => self.title,
:payload => {
:content => self.content,
:tags => self.tags,
:title => self.title
}
}
}.as_json
end
Is it possible to have _search and _suggest working together with the same model ?
I'm just digging into elasticsearch, but, as far as i understand, you can add what you had before modifying in the serializer function and recreate indices, they will live together well in the db. For example:
def as_indexed_json(options={})
{
:name => self.title,
:suggest => {
:input => self.title,
:output => self.title,
:payload => {
:content => self.content,
:tags => self.tags,
:title => self.title
}
}
}.as_json.merge(self.as_json) # or the customized hash you used
To avoid indices redundancy you can look at aliases and routing.
I'm trying to do something like this:
render :json => r.to_json(:methods => [:food_item => {:method => :price_value}])
but it's not working. Is something like this even possible?
thx
edit 1
no association
def food_item
MenuItem.find(food_id)
end
Is food_item an ActiveRecord association? If so, you could try
render :json => r.to_json(:include => { :food_item => { :only => :price_value } })
I'll refine my answer in response to "edit 1". First, remove your food_item method and add an actual association like this:
belongs_to :food_item, :class_name => "MenuItem", :foreign_key => "food_id"
and then do
render :json => r.to_json(:include => { :food_item => { :only => [:price_value] } })
I'm using the RABL gem to render JSON user data for users of comments which are children of annotations which are children of images. I'd like to do something similar to:
object #image
node :annotations do
#image.annotations.map { |a| {
:id => a.id,
:x1 => a.x1,
:x2 => a.x2,
:y1 => a.y1,
:y2 => a.y2,
node :comments do
#image.annotations.comments.map { |c| {
:body => c.body,
:user_id => c.user_id,
:name => User.find(c.user_id).name,
:user_icon => user_icon(User.find(c.user_id), 'square', 30)
}}
end
}}
end
I know this isn't valid in RABL, I also tried using child instead of node, but couldn't access the user data that way. How should I go about doing this and whats the proper syntax to make this happen?
I got this answer from #calvin-l. The trick was to just map the a.comments then grab the data from each comment that way:
node :annotations do
#image.annotations.map { |a| {
:id => a.id,
:x1 => a.x1,
:x2 => a.x2,
:y1 => a.y1,
:y2 => a.y2,
:comments => a.comments.map { |c| {
:body => c.body,
:created_at => c.created_at,
:user => {
:id => c.user.id,
:facebook_id => c.user.facebook_id,
:name => c.user.name,
:username => c.user.username
}
}}
}}
end
I'm using this code to convert a model to json. If i try to use an include 2nd level like this:
p = Product.includes({ :variants => { :stocks => :size } }).where(:id => params[:id]).first
render :json => p.variants.to_json(:include => { :stocks => { :include => :size } })
I receive this error:
undefined method `macro' for nil:NilClass
How I can solve that?
Try this:
render :json => p.variants.map { |v| v.as_json(:include => {:stocks => {:include => :size}}) }
Info about Object#as_json/to_json here.