%div{:class => [#item.type, #item == #sortcol && [:sort, #sortdir]] } Contents
could render as any of:
<div class="numeric sort ascending">Contents</div>
<div class="numeric">Contents</div>
<div class="sort descending">Contents</div>
<div>Contents</div>
I don't really understand the #sortcol && [:sort, #sortdir] part of this snippet.
This relies on operator precedence. So it is evaluated like this:
#item == #sortcol is either true or false.
when false
&& returns false because the other part is not evaluated
hence the code reduces to :class => [#item.type]
when true
&& returns the second part of the expression. In this case the array [:sort, #sortdir]
HAML automatically flattens the array before rendering thus it's equivalent to :class => [#item.type, :sort, #sortdir]
[#item.type, #item == #sortcol && [:sort, #sortdir]]
=>
# if #item.type is blank, so class is still empty
# if #item.type is "numeric" class is "numeric"
class = #item.type
# if #item equal #sortcol
# class will append "sort" and #sortdir if it is not empty.
if #item == #sortcol
class += "sort"
class += #sortdir
end
This construction #item == #sortcol && [:sort, #sortdir] will return [:sort, #sortdir] only if #item == #sortcol is true
Related
I have an Array of Hashes
[
{ :user_id => 123,
:start_date => Date,
:end_date => Date
},
{ :user_id => 345,
:start_date => Date,
:end_date => Date
},
...
]
I want to select some of the objects based on optional parameters, say if params[:user_id], params[:start_date], params[:end_date] is optionally passed from the controller. How do I include multiple conditionals within a single .select statement.
Array.select do |arr|
arr[:user_id] == params[:user_id] if params[:user_id] &&
arr[:start_date] >= params[:start_date] if params[:start_date] &&
arr[:end_date] <= params[:end_date] if params[:end_date]
end
wasn't working as intended. The 2nd and 3rd conditional is ignored.
A pattern I like to use is next. This is basically a way to 'return early' from the block, and split up your conditions into multiple statements. Just make sure you add a true at the end so that if the item passes all the validations, it will be included in the result.
result = array.select do |item|
if params[:user_id]
next if item[:user_id] != params[:user_id]
end
if params[:start_date]
next if item[:start_date] <= params[:start_date]
end
if params[:end_date]
next if item[:end_date] >= params[:end_date]
end
true
end
you could of course change all these if .. else blocks to one liners if you prefer:
result = array.select do |item|
next if params[:user_id] && item[:user_id] != params[:user_id]
next if params[:start_date] && item[:start_date] <= params[:start_date]
next if params[:end_date] && item[:end_date] >= params[:end_date]
true
end
I am currrently in the process of writing a new rails application and have a div near the top of my page called background that contains an image, a Title and some sub title text.
At the moment I have created seperate helper methods, as below, that passes in each of the elements by testing against the action and controller parameter.
This doesn't however seem like a very efficient way of implementing the code and so I was wondering what would be the best way to pass the three elements together in one method?
Currently I have a setup like this:
def background
if params[:action] == "index" && params[:controller] == "everydays"
return "/assets/everyday.jpg"
elsif params[:action] == "index" && params[:controller] == "mens"
return "/assets/mens.jpg"
elsif params[:action] == "index" && params[:controller] == "womens"
return "/assets/womens.jpg"
end
end
def title_h1
if params[:action] == "index" && params[:controller] == "everydays"
return "Everyday"
elsif params[:action] == "index" && params[:controller] == "mens"
return "Mens"
elsif params[:action] == "index" && params[:controller] == "womens"
return "Womens"
end
end
def title_h3
if params[:action] == "index" && params[:controller] == "everyday"
return "Example text for this Everyday Section"
elsif params[:action] == "index" && params[:controller] == "mens"
return "Example text for this Mens Section"
elsif params[:action] == "index" && params[:controller] == "womens"
return "Example text for this Womens Section"
end
end
Any advice people can offer would be much appreciated.
You're right to be suspicious, that is a terrible code smell, and has surprisingly spilled over into the answers! I'll try to bring a little sanity to this page.
Use content blocks for your page-specific background image
# views/layouts/application
<% if content_for?(:background_image) %>
<%= yield(:background_image) %>
<% else %>
<img src="default" />
<% end %>
# views/everydays/index
<% content_for :background_image %>
<img src="/assets/everyday.jpg" />
<% end %>
Use locale files for page-specific text
# locales/en.yml
everydays:
index:
title_h1: 'Everyday'
title_h3: 'Example text for this Everyday Section'
# views/everydays/index
<h1><%= t('everydays.index.title_h1') %></h1>
If the h1 tag is not within your index templates, as in, is a site-wide tag within your application template, then you can use code blocks as described above, or you could create a helper which fetches the relevant title based on the controller:
# application_helper.rb
def title(tag)
t("#{params[:controller]}.#{params[:action]}.title_#{tag}")
end
# usage
<h1><%= title('h1') %></h1>
As you can see, there are many ways to approach this, and even improve on the above.
I would start working with hashes as it eliminates the many tests the if clauses are doing (so the following would all be helper methods):
def fragments
return {
"everydays#index" => {
:background => "/assets/everyday.jpg",
:title => "Everyday",
:description => "Example text for this Everyday Section"
},
"mens#index" => {
...
}
...
}
end
def fragment_for(segment)
fragment = fragments["#{params[:controller]}##{params[:action]}"]
fragment ? fragment[section] : nil
end
You can then put into your views:
<%= fragment_for :background %>
or
<%= fragment_for :title %>
etc.
BTW, plural of "man" is "men" and plural of "woman" is "women".
You can have more than one return value in a method, like so:
def my_helper
if action == 'something'
return 'first return', 'second return', 'third return'
else
return 'first return 2', 'second return 2', 'third return 2'
end
end
You can then call this method:
first_value, second_value, third_value = my_helper
<%= first_value %>
<%= second_value %>
<%= third_value %>
You can just use a single method (say for ex : get_css_attribute_values) and return a hash where the key is name of the CSS attribute and the value is the string which you want to pass.
def get_css_attribute_values
if params[:action] == 'index' && params[:controller] == 'everydays'
return {:background_image => '/assets/everyday.jpg', :title_h1 => 'Everyday', :title_h3 => 'Example text for this Everyday Section' }
elsif params[:action] == 'index' && params[:controller] == 'mens'
return {:background_image => '/assets/mens.jpg', :title_h1 => 'Mens', :title_h3 => 'Example text for this Mens Section'}
elsif params[:action] == 'index' && params[:controller] == 'womens'
return {:background_image => '/assets/womens.jpg', :title_h1 => 'Womens', :title_h3 => 'Example text for this Womens Section'}
end
end
Then you can access all these attributes in the view as :
get_css_attribute_values[:background_image]
get_css_attribute_values[:title_h1]
get_css_attribute_values[:title_h3]
I have a pretty elaborate set-up to change the condition from passing to failing for four composing methods of a method called eligible?.
describe "#participant_age_eligible?" do
it "returns whether participant is age-eligible" do
#part.participant_age_eligible?(#pers).should == true
end
it "returns false if participant is not age eligible" do
q = #survey_section.questions.select { |q| q.data_export_identifier ==
"#{OperationalDataExtractor::PbsEligibilityScreener::
INTERVIEW_PREFIX}.AGE_ELIG" }.first
answer = q.answers.select { |a| a.response_class == "answer" && a.reference_identifier == "2" }.first
Factory(:response, :survey_section_id => #survey_section.id, :question_id => q.id, :answer_id => answer.id, :response_set_id => #response_set.id)
#part.participant_age_eligible?(#pers).should == false
end
end
describe "#participant_psu_county_eligible?" do
it "returns whether participant lives in eligible PSU" do
#part.participant_psu_county_eligible?(#pers).should == true
end
it "returns false if participant coes not live in an eligible PSU" do
q = #survey_section.questions.select { |q| q.data_export_identifier ==
"#{OperationalDataExtractor::PbsEligibilityScreener::
INTERVIEW_PREFIX}.PSU_ELIG_CONFIRM" }.first
answer = q.answers.select { |a| a.response_class == "answer" && a.reference_identifier == "2" }.first
Factory(:response, :survey_section_id => #survey_section.id, :question_id => q.id, :answer_id => answer.id, :response_set_id => #response_set.id)
#part.participant_psu_county_eligible?(#pers).should == false
end
end
There are two more methods just like those two. What I'd like to do is extract the
q = #survey_section.questions.select { |q| q.data_export_identifier ==
"#{OperationalDataExtractor::PbsEligibilityScreener::
INTERVIEW_PREFIX}.AGE_ELIG" }.first
answer = q.answers.select { |a| a.response_class == "answer" && a.reference_identifier == "2" }.first
Factory(:response, :survey_section_id => #survey_section.id, :question_id => q.id, :answer_id => answer.id, :response_set_id => #response_set.id)
portion into a method in the before block and then pass the relevant parameters, but I hesitiate because I have never seen someone define a method in a before block, not even sure you can do it, further, I'm not sure if you even should do it even if you can, maybe it's pointing to a problem that I'm not seeing. So I though I'd ask the unfathomably enormous expertise of the SO community. Thanks.
You can always define methods within your specific rspec file, and then call them from anywhere in that file. For example:
# your_file_spec.rb
describe MyModel do
before(:each) { setup_variables }
describe ...
end
# I usually put my helper methods at the bottom
def setup_variables
# Do some work
end
end
You can also sometimes use a 'scenario outline' approach to your work, for example:
# your_file_spec.rb
describe MyModel do
examples = [{:name => "Joe", :login => "joe18"}, {:name => "Grace", :login => "grace12"}]
examples.each do |example|
it "logs in #{example[:name]}." do
# Do some work
end
end
end
You may also find that useful.
controller code:
def create
if current_user.id != params[:friend_id]
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 1 }
else
#return = { :curr_user_id => current_user.id, :params_id => params[:friend_id], :debug => 2 }
end
render :json => ActiveSupport::JSON.encode( #return )
end
so current_user.id is 1 and params[:friend_id] is being passed via an ajax call and its value is also 1
the problem is that it always returns true when it should return false in this case... is there anything that has to do with the number 1 being parsed as string instead of integer?
params are always strings, use:
if current_user.id != Integer(params[:friend_id])
I don't recommend to_i, look why:
"abc".to_i # => 0 which is unexpected
Integer("abc") # => raises error, which is fine
I have this call in my vote model:
fires :vote_updated, :on => :update,
:actor => :user,
:secondary_subject => :video,
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && (vote.video.user != current_user)}
In case you aren't familiar, it works with the timeline_fu plugin.
I do not want the call to be fired if the user who owns the voted up video is the current user. That is where this line comes in:
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && (vote.video.user != current_user)}
However, I do not have access to current_user here. How do I get around this?
Here's the create method in my votes controller (there actually is no update method):
def create
#video = Video.find(params[:video_id])
#vote = current_user.video_votes.find_or_create_by_video_id(#video.id)
if #vote.value.nil?
if params[:type] == "up"
#vote.value = 1
else
#vote.value = -1
end
elsif (params[:type] == "up" && #vote.value == 1) || (params[:type] == "down" && #vote.value == -1)
#vote.value = 0
elsif ((params[:type] == "up" && #vote.value == -1) || (params[:type] == "down" && #vote.value == 1)) || (#vote.value == 0)
if params[:type] == "up"
#vote.value = 1
else
#vote.value = -1
end
end
if #vote.save
respond_to do |format|
format.html { redirect_to #video }
format.js
end
else
respond_to do |format|
format.html
format.js
end
end
end
I believe the right thing to do would be validating this in controller. I would create a before filter for this case
UPDATE:
Just as a quick example:
before_filter :valid_vote, :only => :update
def update
#vote.update_attributes(params[:vote]) # or whatever
end
..
private
def valid_vote
#vote = Vote.find params[:id]
unless ( #vote.video.user.id != current_user.id )
render :text => 'You can't vote for your own video', :status => 403
end
end
So #vote is being declared and validated before your 'update' action is proccessed.
If it's not valid then your 'update' action stays untouched
UPDATE 2 :
not sure how you'll like it, but you could also do as follows:
in your Vote model:
attr_accessor :skip_timeline
then use the concept with before filter, but do #vote.skip_timeline = true instead of rendering text
then the statement might look as follows:
:if => lambda { |vote| ((vote.value == 1) || (vote.value == -1)) && !vote.skip_timeline }
You could also move ((vote.value == 1) || (vote.value == -1)) to your before filter :
def valid_vote
#vote = Vote.find params[:id]
unless ( [1,-1].include? #vote.value && #vote.video.user.id != current_user.id )
#vote.skip_timeline = true
end
end
and
:if => lambda { |vote| !vote.skip_timeline }
You are getting this error because it's typically not recommended to access current_user (or session information) in your model. I am not all that familiar with the timeline_fu gem, so this answer isn't going to be the greatest answer you may get. I'm merely going to show you how to access current_user from any model.
First go to your application controller. You'll want to make a method that sets the current user. You need to call the method in the before filter.
before_filter :loadCurrentUser
def loadCurrentUser
User.currentUser = current_user
end
Then in your User model, you need to define 'currentUser'.
def self.currentUser
Thread.currentUser[:user]
end
You don't necessarily have to declare the current_user in the application controller, but since it's a gem, I'm not sure if it has an easily accessible controller.
Edit: This way may be prone to problems, but I'm not entirely sure if you were asking how to make current_user available in models, or a completely different workaround so you do not have that problem... and reading the responses of the other answer, I'm thinking it's not what you were asking.