haml two spaces issue - haml

When using haml I have following problem.
First I want to check one variable and after that render something else, but it still should be nested.
Let me explain
code:
.a
.b
gives: <div class=a><div class=b></div></div>
When I use haml if else, I can't nest .b inside .a:
- if id == 3
.a{style => 'xxx'}
- else
.a{style => 'yyy'}
.b <-- 2 spaces, otherwise it fails. but 2 spaces are causing the following issue:
The problem is, because there is no end in haml, I don't know how to put .b within .a div, in both situations (if id ==3, or else).
As an real live example:
- if home != nil
.home
home.id
- else
.home
empty
- end <--- can't use
- if room != nil <-- without end, this will be executed only if home == nil but I wan't this to be executed always
room.id
- else
empty
because haml doens't support -end, I can't use it. If I don't use it, haml automatically closes div if I start to cehck 'room's values because these are in the same line :)
Example with comparing variable to nil, or doing something only if the variable is nil is just an example. I'm looking for a solution that solves such problem with indenting after else so it applies to the whole statement.
There are similar question: HAML: Indenting if/else statements with common content
So, in your case you would do like:
.a{ :style => id == 3 ? 'xxx' : 'yyy' }
.b
edited:
In case of if... elsif... elsif... you can write your own helper:
.a{ :style => select_style(variable) }
.b
#helper method
def select_style(val)
case val
when "3"
"xxx"
when "4"
"yyy"
else
"zzz"
end
end
Of course you can write it all in haml, but it will be ugly:
- if id == "3" then val="xxx"
- elsif id == "4" then val="yyy"
- else val="zzz"
.a { :style => val }
.b
HAML has its advantages, but it is an example of one of the disadvantages.
edited
Or you can going mad and do like:
.a{:style => if var == "3" then "xxx" elsif var == "4" then "yyy" else "zzz" end}

You could also try this:
- if contition
- attrs = { class: '..', id: '..' }
- else
- attrs = { style: '...' }
.a{attrs}
.b

There are similar question: HAML: Indenting if/else statements with common content
So, in your case you would do like:
.a{ :style => id == 3 ? 'xxx' : 'yyy' }
.b
edited:
In case of if... elsif... elsif... you can write your own helper:
.a{ :style => select_style(variable) }
.b
#helper method
def select_style(val)
case val
when "3"
"xxx"
when "4"
"yyy"
else
"zzz"
end
end
Of course you can write it all in haml, but it will be ugly:
- if id == "3" then val="xxx"
- elsif id == "4" then val="yyy"
- else val="zzz"
.a { :style => val }
.b
HAML has its advantages, but it is an example of one of the disadvantages.
edited
Or you can going mad and do like:
.a{:style => if var == "3" then "xxx" elsif var == "4" then "yyy" else "zzz" end}

Related

Rails ActiveRecord where clause

I want to select Cars from database with where clause looking for best DRY approach for my issue.
for example I have this two parameters
params[:car_model_id] (int)
params[:transmission_id] (int)
params[:from_date]
params[:to_date]
but I dont know which one will be null
if params[:car_model_id].nil? && !params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:transmission_id => params[:transmission_id])
else
return Car.where(:transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
elseif !params[:car_model_id].nil? && params[:transmission_id].nil?
if params[:from_date].nil? && params[:from_date].nil?
return Car.where(:car_model_id=> params[:car_model_id])
else
return Car.where(:car_model_id=> params[:car_model_id], :date => params[:from_date]..params[:to_date])
end
else
return Car.where(:car_model_id=> params[:car_model_id], :transmission_id => params[:transmission_id], :date => params[:from_date]..params[:to_date])
end
what is best approach to avoid such bad code and check if parameter is nil inline(in where)
You can do:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
and then:
Car.where(car_params)
Explanation: Since, you're checking if the particular key i.e.: :car_model_id and transmission_id exists in params. The above code would be something like this when you have just :transimission_id in params:
Car.where(:transmission_id => '1')
or this when you have :car_model_id in params:
Car.where(:car_model_id => '3')
or this when you'll have both:
Car.where(:transmission_id => '1', :car_model_id => '3')
NOTE: This will work only when you have params keys as the column names for which you're trying to run queries for. If you intend to have a different key in params which doesn't match with the column name then I'd suggest you change it's key to the column name in controller itself before slice.
UPDATE: Since, OP has edited his question and introduced more if.. else conditions now. One way to go about solving that and to always keep one thing in mind is to have your user_params correct values for which you want to run your queries on the model class, here it's Car. So, in this case:
car_params = params.slice(:car_model_id, :transmission_id).reject{|k, v| v.nil? }
if params[:from_date].present? && params[:from_date].present?
car_params.merge!(date: params[:from_date]..params[:to_date])
end
and then:
Car.where(car_params)
what is best approach to avoid such bad code and check if parameter is
nil inline(in where)
Good Question !
I will make implementation with two extra boolean variables (transmission_id_is_valid and
car_model_id_is_valid)
transmission_id_is_valid = params[:car_model_id].nil? && !params[:transmission_id].nil?
car_model_id_is_valid = !params[:car_model_id].nil? && params[:transmission_id].nil?
if transmission_id_is_valid
return Car.where(:transmission_id => params[:transmission_id])
elseif car_model_id_is_valid
return Car.where(:car_model_id=> params[:car_model_id])
....
end
I think now is more human readable.
First, I would change this code to Car model, and I think there is no need to check if params doesn't exists.
# using Rails 4 methods
class Car < ActiveRecord::Base
def self.find_by_transmission_id_or_model_id(trasmission_id, model_id)
if transmission_id
find_by trasmission_id: trasmission_id
elsif model_id
find_by model_id: model_id
end
end
end
In controller:
def action
car = Car.find_by_transmission_id_or_model_id params[:trasmission_id], params[:car_model_id]
end
edit:
This code is fine while you have only two parameters. For many conditional parameters, look at ransack gem.

Ternary operator not working in Haml

This question has been asked but the answers have not worked. The problem I am having is this hamlc code:
.UI_feed_item.deletable.clearfix{ :class => #feed.fav_post ? 'favorited' : '', feed_id: "#{#feed.id}", id: "feed_item_#{#feed.id}" }
*a lot more haml that doesn't have to do with this question*
the indentation is correct - it shows up weird on here
I want an extra class added to say favorited if feed.fav_post is true. for some reason it added a class 'true' or 'false' instead. I have also tried this:
.UI_feed_item.deletable.clearfix{ :class => (#feed.fav_post ? 'favorited' : ''), feed_id: "#{#feed.id}", id: "feed_item_#{#feed.id}" }
same result
I cannot do an if/else thing because there is no end in haml and I would have to rewrite a hundred lines of indented code. please help! none of the other solutions on the web have worked
Your second shot should work fine. The first variant returns true/false because the hash rocket wins over the ternary operator in terms of precedence - but shouldn't it break on syntax error after that?
You can do if-else in haml.
- if true
some stuff here
- else
some other stuff here
Indentation is used instead of end.
HAMLC doesn't support the ?/: ternary operators, but you can still achieve what you want using inline if/then/else. Try this:
.UI_feed_item.deletable.clearfix{ :class => "#{ if #feed.fav_post then 'favorited' else '' }", feed_id: "#{#feed.id}", id: "feed_item_#{#feed.id}" }

simple_form selected: by name

I have a simple_form collection which contains a list of languages. I want to select 'German' by default, but the selected: option in simple_form requires an id. I could obtain the id of 'German' but would hope that wasn't necessary.
= f.association :language, selected: // not sure what to put here
This works, but stinks (I will NOT be using such atrocious code):
= f.association :language, selected: Language.where("name = 'German'").first.id
I would hope for something like:
= f.association :language, selected: { |lan| lan.name == 'German' }
Every example I've found during the last hour involves the id. Not one example of how to select via the name.
Yeah, SimpleForm can accept proc for selected option. In your case the code is:
= f.association :language, selected: lambda { |lan| lan.name == 'German' }

Parse a URL and remove end portion

I am trying to parse URLs. For example where I am trying to pull out:
~/locations/1 => [locations,1]
~/locations/1/comments => [locations,1]
~/locations/1/comments/22 => [locations,1]
~/locations/1/buildings/3 => [buildings,3]
~/locations/1/buildings/3/comments => [buildings,3]
~/locations/1/buildings/3/comments/34 => [buildings,3]
The format is pretty consistent. I started with arrays but it seems to still fail:
#request_path = request.path.downcase.split('/')
#comment_index = #request_path.index("comments").to_i
if #comment_index > 0
#request_path = #request_path.drop_while { |i| i.to_i >= #comment_index }
end
resource, id = #request_path.last(2)
I added the downcase just incase someone manually typed in an uppercase URL. The drop_while seems to not be working.
What kind of output you have after processing your code?
Edited
Your problem is that you convert element to_i and it is 0. But you want to compare index of element, but can normally get index of element in that situation using Array#index method.
Correct approach:
#request_path.drop_while { |i| #request_path.index(i) >= #comment_index }
You can parse path without drop_while.
My solution:
def resource_details(path)
resource_array = path.downcase.split("/").reject!(&:empty?)
key = resource_array.index("comments")
return key.present? ? (resource_array - resource_array[key..key + 1]).last(2) : resource_array.last(2)
end
It will cut out ["comments"] or ["comments","2"] for your path.
Invoke that method:
1.9.3p0 :051 > resource_details("/locations/1/buildings/3/comments")
=> ["buildings", "3"]
1.9.3p0 :052 > resource_details("/locations/1/comments/2")
=> ["locations", "1"]

Refactoring controller

As I'm sure you can tell by the following code, I'm a newbie. But I have managed to get this to work correctly...
However, I'd really like to know how to refactor this code, as I'm sure its not the best way to do it. Can anyone point me in the right direction?
Thank you very much in advance...
current_controller = params[:controller]
if current_controller == "menus" && params[:id].present?
#menu = Menu.find(params[:id])
elsif current_controller == "menus" && params[:id].nil?
#menu = Menu.first
elsif current_controller == "items" || current_controller == "categories"
#menu = Menu.find(params[:menu_id])
else
#menu = Menu.last
end
A naive translation into (slightly) smaller code:
current_controller = params[:controller]
#menu = if current_controller == "menus"
params[:id].present? Menu.find(params[:id]) : Menu.first
elsif current_controller == "items" || current_controller == "categories"
Menu.find(params[:menu_id])
else
Menu.last
end
Where does this code live?
Would it make more sense to have this in a base app controller, or filter, etc. and override in the three controllers that are special-cased? Or is this wrapped up in a helper, or...?
Edit Using Procs.
# Default if hash entry not found.
menus = Hash.new(Proc.new { |p| Menu.last })
# Items and categories controllers
itemcats = Proc.new { |p| Menu.find(p[:menu_id]) }
menus["items"] = menus["categories"] = itemcats
# Menus controller
menus["menus"] = Proc.new { |p| p[:id] ? Menu.find(p[:id]) : Menu.first }
#menu = menus[params[:controller]].call(params)
(More or less.)
This would be my refactoring to your code:
#menu = case controller.controller_name
when "menus"
if params[:id]
Menu.find(params[:id])
else
Menu.first
end
when "items" || "categories"
Menu.find(params[:menu_id])
else
Menu.last
end
This is untested, but you could try using the case statement as davenewton said
# Case on an expression:
#menus = case params[:controller]
when "menus" && params[:id].present? then Menu.find(params[:id])
when "menus" && params[:id].nil? then Menu.first
when "items", "categories" then Menu.find(params[:menu_id])
else Menu.last
end
You can replace the "then's" with semicolons if you prefer