How can I temporarily disable PaperTrail when reifying a version? - ruby

I am using paper_trail for undo/redo functionality in my site and am having a problem when I call reify.save on a version in that on save and new PaperTrail::Version gets created.
Is there a way to turn off PaperTrail during the saving of a reified object?
I understand that PaperTrail.enabled = false is possible, but I don't want other changes being made a the same time to not be recorded.
My ideal solution would be something along the lines of:
PaperTrail.disable { version.reify.save }

I once accomplished something similar by mixing in something like this:
def without_papertrail
PaperTrail.disable
yield if block_given?
PaperTrail.enable
end
Then you can do something similar to your objective
without_papertrail { version.reify.save }

You can disable paper trail for a particular model, using either of two syntaxes:
m = MyModel.find(123)
m.paper_trail.without_versioning do
# No versioning of `m` happens in this block
end
Note: Since it is called on a model instance, it seems as though this might naturally disable versioning on just that instance, but this syntax disables versioning on the entire model.
The other syntax:
MyModel.paper_trail.disable
# No versioning of MyModel happens here
MyModel.paper_trail.enable

As of today, gem version 10.3.0, the correct way to achieve this is, as per the gem documentation:
PaperTrail.request.disable_model(Banana)
# changes to Banana model do not create versions,
# but eg. changes to Kiwi model do.
PaperTrail.request.enable_model(Banana)

from the readme: https://github.com/paper-trail-gem/paper_trail#7-testing
PaperTrail.enabled = false

Related

ActiveStorage::Attachment find resource by blob_id

I have the following model
class Document
has_many_attached :previews
...
end
And I'm trying to find single elements there.
The problem is if I do:
#document.previews.find_by(blob_id: 22)
I get this error: undefined method `find_by' for #<ActiveStorage::Attached::Many>
So I'm kind of forced to loop through enumerable:
#document.previews.find { |p| p.blob_id == 22 }
Is there any other (better/prettier) way to do this?
#ntonnelier I have a similar model, and with Rails 7.0.3 your first example works fine for me:
#document.previews.find_by(blob_id: 22)
Another couple of options that work are:
#document.previews.where(blob_id: 22)
#document.previews.blobs.find_by_id(22)
You should be able to access the blobs for a particular record via the blobs method, which gets you an ActiveRecord collection, and you can use find on that one.
Something like #document.previews.blobs.find(22) might work in your particular case.

How do I programmatically set a content_security_policy?

I'm configuring the Content Security Policy for our Rails 5.2 app. I need to whitelist some domains in our CSP. I'd like to put the list of domains elsewhere so I can reference them in other places in the application, then generate the CSP headers programmatically from that list.
Looking at the source code for the Content Security Policy configuration mechanisms in Rails 5, it looks like there's some magic metaprogramming going on, so it's not clear to me how to accomplish what I need to do. It looks like the functions I need to call to set headers might be picky about how exactly they want to be called. In particular, it's not clear to me if I can pass them arrays or safely call them multiple times, or if they do some metaprogramming magic that only works if the domains are passed in as individual function arguments.
Can I pass in an array to the header I want to set, like this?
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
csp.child_src whitelisted_domains
end
end
Or can I call the same function multiple times, like this?
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
whitelisted_domains.each {|domain| csp.child_src domain}
end
end
If neither of those will work, what's the best way of accomplishing what I want to do?
From what I can tell from sourcecode and documentation, it takes an array. From the edgeguides at rails, posting following
Rails.application.config.content_security_policy do |policy|
policy.default_src :self, :https
...
end
and the sourcecode, using *sources as param; it believe it takes any number of arguments, meaning you could do something along the lines of;
whitelisted_domains = ['https://example.com', 'self']
Rails.application.configure do
config.content_security_policy do |csp|
csp.child_src(*whitelisted_domains)
end
end
https://blog.sqreen.io/integrating-content-security-policy-into-your-rails-applications-4f883eed8f45/
https://edgeguides.rubyonrails.org/security.html#content-security-policy
Sourcecode of define_method for each directive
https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/content_security_policy.rb#L151
(note: None of this has been tested in a Rails app, simple looking guides and source code of Rails)

How should I merge hash while using 'first_or_create'

I have this hash, which is built dynamically:
additional_values = {"grouping_id"=>1}
I want to merge it with this record object after creation via first_or_create:
result = model.where(name: 'test').first_or_create do |record|
# I'm trying to merge any record attributes that exist in my hash:
record.attributes.merge(additional_values)
# This works, but it sucks:
# record.grouping_id = data['grouping_id'] if model.name == 'Grouping'
end
#Not working:
#result.attributes>>{"id"=>1, "name"=>"Test", "grouping_id"=>nil}
I understand that if the record already exists (returned via 'first'), it won't be updated...although that would be a nice option and any recommendations on that are welcome, but the table was just dropped and recreated, so that's not the issue.
What am I missing?
I also tried using to_sym, resulting with:
additional_values = {:grouping_id=>1}
...just in case there was some weirdness I didn't know about...didn't make a difference
The problem is Hash#merge returns a new hash and then you aren't doing anything with that hash, you're just throwing it away. I would also suggest sticking to using the ActiveRecord methods for updating attributes, instead of trying to manipulate the underlying hash, such as using assign_attributes or, if you want to save the record update. Though, you may find the create_with, which can be used with find_or_create_by, useful here:
model.create_with(additional_values).find_or_create_by(name: 'test')
I can't find any documentation that I like (if at all) for first_or_create in recent rails versions, but if you like that more than find_or_create_by, then if we look at the Rails 3 documentation for first_or_create, you should be able to do with out the create_with:
model.where(name: 'test').first_or_create(additional_attributes)

How to extend redcarpet to support auto linking user mentions?

On my rails3 application I want to use redcarpet to handle user's posts and the user comment section. As such I'd like to extend redcarpet to support turning #username into a link to a user on my site. I know redcarpet is written in C but is there anyway easy way to extend it in ruby? How hard would it be to write it in C? Should I just do this outside of redcarpet?
Also I'm intrested in some other extensions of redcarpet that would be shorthand for linking to other models in my app. I'm not sure the syntax yet but I'm guessing it would be similar to how github handles linking to issues.
I found it pretty easy to extend redcarpet's parser in Ruby for my rails 3 app. It wasn't scary at all.
First, start by deriving a class from Redcarpet's HTML renderer and override the preprocess method as recommended in the docs. In Rails 3.2 and Rails 4, this file can go anywhere and you don't need to require it. I use a 'services' folder to hold code like this.
# app/services/my_flavored_markdown.rb
class MyFlavoredMarkdown < Redcarpet::Render::HTML
def preprocess(text)
text
end
end
Next step is to add methods that do text substitutions you want. Here I use regex to wrap text that looks like "#mention" in an HTML span tag with a css class 'mention'.
# app/services/my_flavored_markdown.rb
class MyFlavoredMarkdown < Redcarpet::Render::HTML
def preprocess(text)
wrap_mentions(text)
end
def wrap_mentions(text)
text.gsub! /(^|\s)(#\w+)/ do
"#{$1}<span class='mention'>#{$2}</span>"
end
text
end
end
You could just as easily look up a user's profile page and wrap the #mention in an anchor tag instead. In my case, I also made methods for emoticons and hashtags that worked the same way and chained the methods together.
The last step is to add a helper that accepts some text, creates an instance of your Redcarpet-derived class, passes the text into that for processing, and returns the html result.
# app/helpers/application_helper.rb
def flavored_markdown_to_html(text)
renderer = MyFlavoredMarkdown.new()
# These options might be helpful but are not required
options = {
safe_links_only: true,
no_intra_emphasis: true,
autolink: true
}
Redcarpet::Markdown.new(renderer, options).render(text)
}
In your views you can call it like this:
<%= flavored_markdown_to_html("This is something worth #mentioning") %>
The output would then be:
This is something worth <span class='mention'>#mentioning</span>.
I once tried to extend redcarpet, but found it very difficult. If there are no other dependencies on redcarpet I'd recommend you try rpeg-markdown which is a (somewhat outdated) Ruby gem providing bindings to the excellent peg-markdown.
peg-markdown is a markdown interpreter written as a formal grammar. This means that it is very easy to extend it with own syntax. I've successfully extended peg-markdown for my own projects (see my fork here) and I found it to be much simpler than fiddling with redcarpet's custom parser code.
I also found peg-markdown to have fewer bugs.
The Ruby bindings may have to be made current by updating the git submodule. (I'm planning to submit a pull request to update rpeg-markdown to the latest version of peg-markdown.)

What's a proper way of using enum-type attributes in Rails 3?

Simple example might be a Post, that has three states, DRAFT, PUBLISHED and DELETED.
The way I do this right now is something like:
class Post < ActiveRecord::Base
DRAFT = 0
PUBLISHED = 1
DELETED = 2
end
The problem that arises is that when I'm running my tests using spork, I have to reload the model manualy, with something like
Spork.each_run do
Dir["#{Rails.root}/app/models/**/*.rb"].each { |model| load model }
end
Which in result gives me loads of warnings like
warning: already initialized constant DRAFT
warning: already initialized constant PUBLISHED
warning: already initialized constant DELETED
Everything works just fine, but I don't think this is the best way to do this. Is there a better way to do this? I know there are gems like acts_as_state_machine, but I'd like to know a non-gem solution if there is a simple one.

Resources