rails getting html from Rack::BodyProxy object in middleware - ruby-on-rails

I'm calling controllers from middleware like:
#status, #headers, #articles = ArticlesController.action('index').call(env)
And really, I just want the html from that #articles but the #articles is a huge big Rack::BodyProxy object and the html is buried deep in it.
The stranger thing is that I find it like:
articles_html = #articles.instance_variable_get(:#body).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
but another developer accesses it like this:
articles_html = #articles.instance_variable_get(:#response).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
So that makes for a pretty ugly ternary:
articles_html = #articles.instance_variable_get(:#response).nil? ? #articles.instance_variable_get(:#body).instance_variable_get(:#stream).instance_variable_get(:#buf)[0] : #articles.instance_variable_get(:#response).instance_variable_get(:#stream).instance_variable_get(:#buf)[0]
Plus, I'm not sure if that's going to cover all the cases.
What's a better way to approach this?

I know this is a very old question but in the off chance that anybody stumnles upon this...
Any method call to an instance of Rack::BodyProxy ultimately gets passed on to the resulting body array. That means you can simply call first to retrieve the body string, or call each to iterate through the array.
You can see the delegation here

Just thanks for the previous answer, you can also call .join if you want just have the full body string in a variable.

Related

Cant call method from helper into model

I wish to use this code but I am having difficulty getting it working. It is contained in a model:
def to_pdf
title = #account.name
subtitle = "Account Statement: #{#month_name} #{#year}"
StatementPDF.new(title, subtitle, transactions).render
end
The transactions method is stored in a helper file (dont ask) but I cant seem to call it. Any suggestions.
def transactions(account_id, start, finish)
return #transactions if #transactions
response = get_call('/Accounts/Statements/' + account_id.to_s + '/' + start + '/' + finish)
response = JSON.parse(response.body)
#transactions = response.map {|txn| Transaction.new(txn)}
end
I have had suggestions regarding placement of my transactions method but have to have it there for reasons above my paygrade. The strangest thing about this is that it worked at a point and when i reloaded the environment just to check everything was working as should be it stopped working again. Any suggestions?
(Moved to answer for readability, but it's more an extended comment.)
Helpers are for the view.
Making HTTP requests from the view layer is bad because (a) it puts too much logic into the view layer, and (b) it's counter-intuitive and could be misused, e.g., someone could accidentally (or purposefully) call it multiple times, potentially incurring the cost twice.
(Partially alleviated because of the sort-of caching, depending on access to #transactions.)
Something like this belongs in a utility class, called from the controller. (In general only the controller should generate data passed into the view.) That makes the controller easier to test because you can trivially mock/stub a top-level utility class method instead of having to use a fake web layer, mocking get_call, etc.
Unrelated, but Ruby favors string interpolation over concatenation, e.g.,
response = get_call("/Accounts/Statements/#{account_id.to_s}/#{start}/#{finish}")
I'm also not a fan of raw calls like this and prefer to see them behind some form of API mechanism, particularly if this is a pattern used throughout the app, something like:
def get_transactions_data(account_id, start, finish)
response = get_call("/Accounts/Statements/#{account_id.to_s}/#{start}/#{finish}")
JSON.parse(response.body)
end
(I might even create a URL builder as well, but I like really, really concise, communicative code.)
The step of transforming them into Transaction objects may or may not belong there as well, it depends on how your app is laid out, and how strongly your layers are separated.

Caching DB Result As Global Var

Hi I currently have a helper method that gets Klass.all.map{|m| m.name}. Now I use ids to get the name from the array and if I add more it'll automatically update.
When I use this helper method in a loop in the view, I think it will make multiple queries to get the Klass each time which means a lot of extra work.
I was wondering how I can "cache" this array or if I should be doing this a better way.
Thanks!
SQL caching is done automatically if you're within the same action. You can see here for a more detailed explanation. Just by the way, it would probably be more efficient to use pluck, as in Klass.pluck(:name). This would optimize your SQL query.
Your helper method should look similar to this
def klass_names
#klass_names ||= Klass.all.map{|m| m.name}
end

Read json serialised objects back from a file

I am aiming to serialise a set of objects into a file so as to create a backup. I have the start of that working, using a methods on the models (simplified here, assuming I have two ActiveRecords foo and bar):
def backup(file, foo, bar)
file.write(foo.to_json(root: true))
file.write(bar.to_json(root: true))
end
This gives me a file as I desire, in this case with two records:
{"foo":{"Account_id":1,"Name":"F","created_at":"2013-04-16T10:06:19Z","id":1,"updated_at":"2013-04-20T11:36:23Z"}}
{"bar":{"Account_id":1,"Name":"B","created_at":"2013-04-16T10:06:19Z","id":1,"updated_at":"2013-04-20T11:36:23Z"}}
At a later date I then want to read that backup in and reinstantiate those objects, probably then persisting them back to the database. My aim is to iterate through the file checking the type of each object, then instantiating the right object.
I have part of the logic, but not yet all of it, I haven't worked out how I determine the type of each serialised object before I instantiate it. The code I have for a restore is as follows:
def restore(file)
file.each_line do |line|
**<some magic that parses my line into objectType and objectHash>**
case objectType
when :foo
Foo.new.from_json(objectHash)
Foo.process
Foo.save!
when :bar
Bar.new.from_json(objectHash)
Bar.process
Bar.save!
end
end
end
What I'm looking for is the bit that goes in the "some magic" section. I can just write the code to parse the line directly to determine whether it's a foo or a bar, but I feel like there's probably some tricky Rails/Ruby way to do this that is automatic. Unfortunately, in this case Google is not being my friend. All I can see are pages that are focused on json in the web requests, but not parsing json back in this way. Is there something I'm missing, or should I just write the code to split the string directly and read the object type?
If I do write the code to split the string directly, I would write something along the lines of:
objectType = line[/^{"(\w*)"=>(.*)}/, 1]
objectHash = line[/{"(\w*)"=>(.*)}/, 2]
This is pretty ugly and I'm sure there's a better way (which I'm still looking into), but I'm not sure that this is even the right approach v's there being something that automatically looks at a json representation and knows from the root value what object to instantiate.
Lastly, the actual instantiation using from_json isn't working either, it isn't populating any of the fields on my ActiveRecord. It gives me nil parameters, so I think the parse syntax isn't right.
So, that makes three questions:
Is there a way to determine which object it is that I'm just missing, that is much cleaner?
If there isn't and I need to use a regexp, is there a syntax to get both bits of the line parsed in a single go, rather than my two lines with the same regexp?
The from_json syntax appears unhappy. Is there a syntax I'm missing here? (no longer a question - the code above is fixed, I was using as_json when it should have been to_json, although the documentation is rather unclear on that....)
(Note: edits over time to clarify my question, and because I've now got a regexp that works (didn't before), but still not sure it's very elegant.)
Further information - one of the problems here, as I dig into it further, is that the as_json isn't actually giving me json - what I have in the file is a hash, not json at all. Further, the values for created_at and lastupdated_at in the hash aren't quoted - so basically that's what's causing the parse on the way back in to fail. I've worked out that I should use to_json instead of as_json, although the documentation suggests that as_json should work.
I'm not sure I fully understand you're methodology, but I think using JSON.parse() would help.
There's some good information here http://mike.bailey.net.au/2011/02/json-with-ruby-and-rails/
This would help you translate the raw object back to a hash.
OK, so I think I've got something that works. I'm not convinced at all that it's elegant, but it gives me the result. I'll spend some time later trying to make it cleaner.
The code looks like this:
file.each_line do |line|
objectType = line[/^{"(\w*)":(.*)}/, 1]
objectJSON = line[/{"(\w*)":(.*)}/, 2]
objectHash = JSON.parse(objectJSON)
case objectType
when 'foo'
restoredFoo = Foo.new(objectHash.except('id', 'created_at', 'updated_at'))
restoredFoo.created_at = objectHash['created_at']
restoredFoo.updated_at = objectHash['updated_at']
restoredFoo.save!
end
when 'bar'
restoredBar = Bar.new(objectHash.except('id', 'created_at', 'updated_at'))
restoredBar.created_at = objectHash['created_at']
restoredBar.updated_at = objectHash['updated_at']
restoredBar.save!
end
end
Items of note:
I feel like there should be a way to create the object that isn't a JSON.parse, but rather would make use of the from_json method on the model. I'm not sure what the from_json is good for if it doesn't do this!!
I'm having fun with mass_assignment. I don't really want to use :without_protection => true, although this would be an option. My concern is that I do want the created_at and updated_at to be restored as they were, but I want a new id. I'm going to be doing this for a number of entities in my application, I didn't really want to end up replicating the attributes_protected in the code - it seems not very DRY
I'm still pretty sure my reg exp can give me both objectType and objectJSON in one call
But having said all that, it works, which is a good step forwards.

Is there any way to define a model's attribute as always html_safe?

I have a model called Feature with a variable called body_string, which contains HTML markup I'd like to render, rather than escape.
Every time I reference body_string in my views, I need to use <%=raw or .html_safe. This seems redundant and not-so-DRY.
Is there any way that I can establish once-and-for-all the body_string variable as html_safe?
I'm assuming this would happen in the app/models/feature.rb file, but I can't figure out what the right syntax would be, exactly. I've thought of this:
def body_string
return self.body_string.html_safe
end
But Rails doesn't like it; it raises a stack level too deep exception.
Naturally I could define a variable/method with a different name:
def safe_body_string
return self.body_string.html_safe
end
And then just change all references in the views from body_string to safe_body_string. But somehow this seems almost as un-DRY as simply using raw or .html_safe in the first place.
Any insights to how best to handle this? I feel like there must be something really elegant that I'm just not seeing.
Just use read_attribute to avoid the recursive call to body_string:
def body_string
read_attribute(:body_string).html_safe
end
read_attribute is complemented by write_attribute for setting attributes from within your model.
A note on style: Don't use explicit returns unless you actually need them. The result of the last statement in a method is implicitly the value returned from the method.
While #meager's answer will definitely work, I don't think this logic belongs in a model. Simply because it adds view-level concerns (HTML safeness) to the model layer, which should just include business logic. Instead, I would recommend using a Presenter for this (see http://nithinbekal.com/posts/rails-presenters/ or find a gem for this -- I personally love Display Case). Your presenter can easily override the body_string method and provide the .html_safe designation when displaying in the view. This way you separate your concerns and can continue to get body_string from other models without mixing in the view concern.
Maybe this gem is useful for you. I also wanted to stop repeating html_safe all the time when the content is completely trustable.
http://rubygems.org/gems/html_safe_attribute
Or you can also use this approach,
def body_string
super && super.html_safe
end

Is it bad practice to constantize parameters submitted through the browser?

I've got a single-table-inheritance setup where I have a single Controller (I felt having multiple would be duplicative). However, for some methods, I'd like to call into the subclasses of the models. I figured I could have the browser send a parameter that I'd write a case statement against. Something like:
case #model[:type]
when "A"
#results = Subclass1.search(params[:term])
when "B"
#results = Subclass2.search(params[:term])
...
end
Alternatively though, I learned that Ruby, in all it's trickery can create a model out of a string. Something like:
#results = params[:model].constantize.search(params[:term])
My question: is this a bad practice? I can imagine someone sneaky could craft a request that would get me to form an arbitrary internal object.. but I could confirm that the object is a subclass of the thing I want..
When doing this, i like to refactor it with case, just to be very clear about my allowed inputs:
#results = case params[:model]
when 'page' then Page
when 'post' then Post
else raise 'finger'
end.search(params[:term])
If you have a whitelist of objects that you check it against before you do it, then you should be ok. You just always want to make sure you are santizing and validating input coming from external sources very throughly to protect yourself.
This snippet uses Ick's maybe for simplicity, but write it as you feel comfortable, the point is simply to use a hash:
#results = {"A" => Subclass1, "B" => Subclass2}[params[:model]].maybe.search(params[:term])

Resources