How to handle optional nested input with Phoenix framework? - phoenix-framework

Phoenix nested form input via optional "inputs_for" is resulting in a map in the controller that fails validation. So for example, a post has comments. If I want to add a comment for a post when the post is created, the nested form would look like:
<%= form_for #changeset, #action, fn p -> %>
<%= text_input p, :body %>
<%= inputs_for p, :comments, fn c -> %>
<%= text_input c, :body %>
<% end %>
<% end %>
I fill in the post body but don't add anything for the comments this time around. That will give us the params["post"] map in the controller of
%{
"body" => "My post",
%{"comments" => %{"0" => %{"body" => ""}}
}
However, the comments are "optional". So, the validation done for comments will fail because there is a body but it is empty.
The question then is how do we remove/scrub comments if it is empty but keep it if it has some value? Scrubbing of params "post" does not seem to be doing the trick.

I guess you will have to write your own scrub_comments function plug that will remove empty comments from comments map and eventually remove comments if it is empty.

Here is the kind of approach Chris mentions. The below is modified from my working version to work with posts and comments, and is untested, but should at least show the approach.
A helper module:
defmodule MyappWeb.Helpers do
alias MyappWeb.Post
def filter_post_params(%Plug.Conn{:params => %{"post" => %{"comments" => _}}} = conn, _opts) do
filtered_params =
conn.params
|> get_in(["post"])
|> Enum.reject(fn({_, x}) -> match?(%{"comments" => nil}, x) end)
|> Enum.into(%{})
new_params = put_in(conn.params, ["post"], filtered_params)
%Plug.Conn{conn | params: new_params}
end
def filter_post_params(%Plug.Conn{} = conn, _opts), do: conn
end
The controller, using Phoenix's scrub_params plug and our filter_post_params plug from above:
defmodule MyappWeb.PostController do
use MyappWeb, :controller
alias Myapp.Post
plug :scrub_params, "post" when action in [:create, :update]
plug :filter_post_params when action in [:create, :update]
# ...
end

Related

How to concatenate #something inside a render function in a template

I have this function in my template:
<%= for {element, id} <- Enum.with_index(MyProject.PageView.Recursion.buildElements(#header_linkNumber),1) do %>
<%= render FabricaASA.ComponentView, #header_linkType,
button_id: "#{id}",
button_mainStyle: #header_mainStyle
%>
<% end %>
Now I would like to concatenate, on my right side, #header_mainStyle + id so that from other template, for each created element, I could pass: header_mainStyle1, header_mainStyle2,...header_mainStyleN
Also, on the left side, where I have button_mainStyle: I would like to concatenate #header_linkType + _mainStyle: so that I could dynamically change it to, link_mainStyle: or button_mainStyle:
Up to now I wasn't able to do it properly...
I'm afraid you are doing something wrong if you need such thing. Maybe there's a simpler solution...
Anyway: since some version of Phoenix (I'm sorry I don't know which one precisely, maybe 1.0?), #-variables are stored in #conn.assigns map and you can access them by name there. In older versions, these variables were macros and this kind of magic did not work.
So you can try to put this into the controller:
def index(conn, _params) do
render conn, "index.html", [var1: "var1"]
end
and this into the page template:
<p>var1: <%= #var1 %></p>
<p>assigns:</p>
<%= for i <- 1..10 do %>
<p>var<%= i %>:<p>
<pre><%=
varname = "var#{i}" |> String.to_atom
inspect(#conn.assigns[varname]) %>
</pre>
<% end %>
...you will see var1 to var10 bindings (screenshot: http://postimg.org/image/4b4790cjz/). But it's little bit black magic and probably wrong approach.

ajax-datatables-rails How to add buttons in ajax response

My problem is that I don't know how to add different buttons that remain in a condition using the gem for server side processing ajax-datatables-rails. For example before trying to do sever side processing I had this in my view.
<% if current_user.sales_manager? || current_user.admin? %>
<td>
<%= link_to t('.destroy', :default => t("helpers.links.destroy")),
bill_sale_path(bill_sale),
:method => :delete %>
</td>
<% end %>
and following the tutorial in the main page of the gem, in bill_sale_datatable.rb I have this.
def data
records.map do |record|
[
link_to('Show', bill_sale_path(record), class: 'btn-xs btn-link'),
record.customer.name,
record.seller.name,
l(record.date_sold, format: :short),
record.payment_type.name,
record.payed,
number_to_currency(record.total),
CONDITION AND BUTTON HERE
]
end
end
Then, how do I use the server side processing to provide different buttons that belongs to an if condition?
While I haven't used the gem, I have used datatables with Rails and this is what I did:
Store the button as a variable before the array and then just call the variable in the array.
records.map do |record|
record_button = ''
record_button = button_to('A', button1_path) if condition1
record_button = button_to('B', button2_path) if condition2
record_button = button_to('C', button3_path) if condition3
record_button = button_to('D', button4_path) if condition4
[
link_to('Show', bill_sale_path(record), class: 'btn-xs btn-link'),
record.customer.name,
record.seller.name,
l(record.date_sold, format: :short),
record.payment_type.name,
record.payed,
number_to_currency(record.total),
record_button
]
end
You could also use a case statement if that suited your needs.
Make sure you add
def_delegator :#view, :button_to
To the top of your file as per:
https://github.com/jbox-web/ajax-datatables-rails#using-view-helpers

Rails 3: Loop shows more records than there should be

Not exactly sure of what to call this issue. Still new to rails.
Situation: An auction contains many lots.
I'm showing an auction's lots in a url like /auctions/3/lots/.
View:
<ul>
<% #lots.each do |lot| %>
<li><%= lot.auction_id %>: <%= lot.id %></li>
<% end %>
</ul>
Outputs this:
<ul>
<li>3: 1</li>
<li>3: </li>
</ul>
I only have one lot in my database. Not sure where the extra loop instance is coming from.
This happens on any lot listing no matter which auction I'm looking at.
Also,
<%= #lots.length %> displays 2
<%= #lots.size %> displays 2
<%= #lots.count %> displays 1
My lots_controller looks like this:
def index
#auction = Auction.find(params[:auction_id])
#lots = #auction.lots
end
def create
#auction = Auction.find(params[:auction_id])
#lot = #auction.lots.build(params[:lot])
if #lot.save
redirect_to auction_lots_path, :notice => 'Lot was successfully created.'
else
render :action => "index"
end
end
My models:
class Auction < ActiveRecord::Base
...
has_many :lots
end
class Lot < ActiveRecord::Base
belongs_to :auction
...
end
The ...s are just attr_accesssible and validates lines.
The log when I hit the page was requested, here it is.
Started GET "/auctions/8/lots" for 127.0.0.1 at 2013-02-13 16:35:51 -0500
Processing by LotsController#index as HTML
Parameters: {"auction_id"=>"8"}
Auction Load (0.1ms) SELECT "auctions".* FROM "auctions" WHERE "auctions"."id" = ? LIMIT 1 [["id", "8"]]
Lot Load (0.2ms) SELECT "lots".* FROM "lots" WHERE "lots"."auction_id" = 8
[#<Lot id: 18, description: "1923 Morgan", lot_number: 1, auction_id: 8, created_at: "2013-02-13 17:20:04", updated_at: "2013-02-13 17:20:04">]
Rendered layouts/_messages.html.erb (0.1ms)
Lot Exists (0.2ms) SELECT 1 AS one FROM "lots" WHERE "lots"."auction_id" = 8 LIMIT 1
Rendered lots/index.html.erb within layouts/application (9.4ms)
Completed 200 OK in 21ms (Views: 17.8ms | ActiveRecord: 0.5ms)
Update:
Someone mentioned that it looks like I'm using #auction.lots.build somewhere.
And yes, I am. I have a form on the same page (index) where I can add lots.
<%= form_for(#auction.lots.build, :url => auction_lots_path(#auction)) do |f| %>
...
<% end %>
Changing #auction.lots.build got rid of the extra row, although now I can't create lots successfully. I'm not sure what to do. I probably have to set up something in the index method of the lots_controller, but I don't know what.
Any help is appreciated.
This would happen in your create method if the lot failed to save. Because you used #auction.lots.build, that appends a lot record to the auction. If it doesn't save properly, it's still there unsaved. That explains why the "mystery" one doesn't have an id, and also why:
<%= #lots.size %> displays 2
<%= #lots.count %> displays 1
#lots.count is a database query, but #lots.size is just the size of the array in memory.
I would probably do something more like this in the create action:
def create
#auction = Auction.find(params[:auction_id])
#lot = #auction.lots.create!(params[:lot])
redirect_to auction_lots_path, :notice => 'Lot was successfully created.'
rescue ActiveRecord::RecordInvalid
render :action => "index"
end
... but of course others will prefer using if/else rather than rescuing the exception. There are other ways around this. You could do #auction.reload.lots to cull the unsaved one, but that's a little wierd. The normal rails thing to do in this case is re-render the form with the validation errors displayed and ask the user to fix them and try creating again.
This should help:
def create
params[:lot].merge!({:auction_id => params[:auction_id]})
#lot = Lot.new(params[:lot])
if #lot.save
redirect_to auction_lots_path, :notice => 'Lot was successfully created.'
else
render :action => "index"
end
end

How to apply a mapping to a value in a Puppet/Ruby ERB template?

I'm writing an ERB template (for a Puppet module) that gets passed an Hash like this:
{"stuff" => {"foo"=>"aaa", "bar"=>"ccc"},
"other" => {"foo"=>"bbb", "bar"=>"ddd"}}
and I'm iterating over it in my templates producing rows of text:
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= v["foo"] %>:<%= v["bar"] %>
<% end -%>
Now I'd like to apply some mapping to the "foo" data with a second hash I'll pass to the template. In pseudocode:
mappings = {"aaa" => "something", "bbb" => "somethingelse"}
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= TRANSLATE_SOMEHOW(v["foo"], mappings) %>:<%= v["bar"] %>
<% end -%>
...in order to get "something" whenever the value was "aaa", and so on. I expect to get the original value if there is no corresponding key in the "mappings".
Doing that kind of thing in Puppet's language is probably possible (by extending it with some Ruby code) I think it is probably more appropriate in the ERB template, but I don't know how to do that and not knowing Ruby isn't helping me - tried google without much success.
I'm looking for code to achieve that in an ERB function or some pointers to relevant documentation for my RTFM pleasure.
EDIT:
for future readers, here's DigitalRoss' answer translated to my ERB example above:
<% #my_data.each_pair do |k, v| -%>
<%= k %> <%= mappings[v["foo"]] || v["foo"] %>:<%= v["bar"] %>
<% end -%>
With the erb stuff removed for clarity, this is what you want to do. (The p() function just prints its argument. You can try this in irb.)
#my_data.each do |k, v|
f, b = v['foo'], v['bar']
p(mappings[f] || f)
p(mappings[b] || b)
end

Ruby on Rails - Truncate to a specific string

Clarification: The creator of the post should be able to decide when the truncation should happen.
I implemented a Wordpress like [---MORE---] functionality in my blog with following helper function:
# application_helper.rb
def more_split(content)
split = content.split("[---MORE---]")
split.first
end
def remove_more_tag(content)
content.sub(“[---MORE---]", '')
end
In the index view the post body will display everything up to (but without) the [---MORE---] tag.
# index.html.erb
<%= raw more_split(post.rendered_body) %>
And in the show view everything from the post body will be displayed except the [---MORE---] tag.
# show.html.erb
<%=raw remove_more_tag(#post.rendered_body) %>
This solution currently works for me without any problems.
Since I am still a beginner in programming I am constantly wondering if there is a more elegant way to accomplish this.
How would you do this?
Thanks for your time.
This is the updated version:
# index.html.erb
<%=raw truncate(post.rendered_body,
:length => 0,
:separator => '[---MORE---]',
:omission => link_to( "Continued...",post)) %>
...and in the show view:
# show.html.erb
<%=raw (#post.rendered_body).gsub("[---MORE---]", '') %>
I would use just simply truncate, it has all of the options you need.
truncate("And they found that many people were sleeping better.", :length => 25, :omission => '... (continued)')
# => "And they f... (continued)"
Update
After sawing the comments, and digging a bit the documentation it seems that the :separator does the work.
From the doc:
Pass a :separator to truncate text at a natural break.
For referenece see the docs
truncate(post.rendered_body, :separator => '[---MORE---]')
On the show page you have to use gsub
You could use a helper function on the index page that only grabs the first X characters in your string. So, it would look more like:
<%= raw summarize(post.rendered_body, 250) %>
to get the first 250 characters in your post. So, then you don't have to deal w/ splitting on the [---MORE---] string. And, on the show page for your post, you won't need to do anything at all... just render the post.body.
Here's an example summarize helper (that you would put in application_helper.rb):
def summarize(body, length)
return simple_format(truncate(body.gsub(/<\/?.*?>/, ""), :length => length)).gsub(/<\/?.*?>/, "")
end
I tried and found this one is the best and easiest
def summarize(body, length)
return simple_format = body[0..length]+'...'
end
s = summarize("to get the first n characters in your post. So, then you don't have to deal w/ splitting on the [---MORE---] post.body.",20)
ruby-1.9.2-p290 :017 > s
=> "to get the first n ..."

Resources