Overriding user_guardian.rb in a plugin (no fork necessary!)

Hey, sorry for the newbie question here. Have been reading the docs but need some guidance, please.

I’ve forked Discourse and made my own changes, which I’d like to then do my own deploy from. The guide on contributing walks you through how to make changes and submit a PR, but in this instance, I don’t want to do a PR. I’d just like to take my tiny change and get a Discourse install working from that. I thought the way to do that was to change this line to my own repo:

https://github.com/discourse/discourse_docker/blob/master/image/base/Dockerfile#L143

I did that and did an install, but the changes I made aren’t showing up, so clearly I’m doing something wrong. Any thoughts? Thanks!

EDIT:
For transparency, and in case someone has an easier way to do this, all I’m trying to do is allow anonymous users to change their usernames and users when they enter anonymous mode. It’s as simple as changing false to true in this line:

https://github.com/discourse/discourse/blob/master/lib/guardian/user_guardian.rb#L45

If there’s a better way, I’m all ears. But this is all I need to enable. I’m skilled with Python and Java and a lot of other back end things, but I know zero Ruby/JavaScript/HTML/etc.

EDIT2:
As per a post I saw elsewhere, I updated app.yml as follows:

run:
- exec:
 cd: /var/www/discourse
 cmd:
    - sudo -u discourse git remote set-url origin https://github.com/my/forked/discourse.git
    - sudo -u discourse git fetch origin
    - sudo -u discourse git checkout origin/master

Still no joy after rebuilding the app. Launches okay but my changes are not present, it seems.

Is it not possible to wrap your changes into a plugin and use that instead?

5 Likes

Everyone who has done that had been very sorry. And you can’t get much help here.

Whatever you’re doing, do in a plugin.

10 Likes

Okay. Any idea how I might do this via plugin?

Hey @leighno5

My thoughts to you are, from personal experience, is that writing plugins for Ruby to do what you are doing in your “fork” is much easier done from a plugin, when we understand Ruby and Rails enough to easily write a plugin for Discourse (or modify any Rails class).

If we don’t understand Rails and Ruby easy enough to write a Discourse plugin, my experience is forking Discourse and hacking on core is “misguided”.

I guess the analogy is this (sorry for this simple idea):

“Someone finds it difficult to walk; so they decide to run instead.”

Let me explain, if you do not mind:

Before I started writing Rails applications (nothing to do with Discourse), and tried to write Discourse plugins, I was a bit lost; and was even annoyed with Discourse a bit, I think. It’s like when you want to hit a golf ball the first time; it does not go straight and takes a lot of work to hit the ball in the middle of the fairway. Forking Discourse is like pulling out the big driver on the range before you can chip and putt with the short irons!

I took a break from hacking Discourse (many months backed) and worked for a while actually building a number of Rails applications from the ground up; and then (and only then) I began to develop some “instinctive” knowledge about Rails. After that, when I decided to modify Discourse (I’m currently running 6 custom plugins I wrote in production for Discourse), everything became intuitive and plugins which modified the Ruby core became too simple.

Ruby is very flexible. We can override any class, any object. We can redefine every aspect of Ruby. With some experience, we start to go “WOW”, I had no idea Ruby was so flexible (and powerful) and start to “become dangerous” because we can fly like superman with Ruby and Rails. We are on the beginning of our Ruby and Rails journey at that time, not the end!

With the Ruby and Rails knowledge I have gained in 2020, knowing what I know now, I would never fork Discourse and make changes to the Ruby and Rails core as you are proposing, because plugins are just too easy to override and modify Ruby classes, when we understand the basics of Ruby classes and the basics of meta-programming.

I guess what I am saying is, sorry to be so direct, is that if someone thinks they need to hack the Discourse core to make some minor Ruby changes; then they don’t understand Ruby and Rails enough; because if they did, they would not hack the core and just write a quick plugin to override the classes and love monkey-patching.

On the other hand, if I was wanted to do something crazy and be miserable like replacing Discourse EmberJS with React and Ant Design, then forking would be the only way to go! However, in my view, “Discourse” is not defined by the “Javascript libs”. Discourse is defined by the skills of the core development team (the people) and their attention to detail, customer service, team approach to open source code development, their SPA feature pipeline, and all their hard work! It would be a bit crazy (in my mind) to toss all that brain power alway just because we might like Ant Design (with React) or VueJS over Ember.

The same is totally true even more if we are just going modify core Discourse here and there! It would be a bit “nutty” to fork it to do that, tossing away the “people” who are the “real” Discourse (not the code).

I am speaking only from my personal journey in 2020 learning Ruby and Rails. Now, it’s becoming easy (the basics) and I’m “dangerous”, LOL. I can “monkey patch around with anything”, which is not alway good; and that is what Discourse plugins are for.

Keep the core solid as a rock, and enjoy “monkeying around” with Discourse with plugins.

Hope this helps.

Note, while writing this reply, you wrote:

This kinda proves my point, don’t you think?

Writing a “plugin” in Discourse is writing “Ruby code” (on one level) and for others it goes much deeper (in EmberJS).

Before you start writing Discourse plugins, my friendly advice, perhaps seeming not of value to you, is to first go develop some Ruby on Rails apps. Learn your way around Ruby and Rails, at least the basics, after that, you will have answered your own question above.

I was on the phone this morning in conference call with a client for three hours going over a Rails app, validations, models, etc. and then I was taking a break after coding up some of the action items from the call, and read your post.

The shortest path to writing Discourse plugins, in my view, is to learn Rails first.

Hope this helps!

4 Likes

Here’s a good starting point:

3 Likes

No you’re exactly correct. I have no experience with Ruby at all, so certainly no offense taken!

I’ve seen the plug-in guide and have been through it a couple of times, but unfortunately my Ruby ignorance continues to frustrate, and I have no clue how I would write a plug-in that would override that little bit of code in the user-guardian file to allow anonymous users to change their names. :confused:

2 Likes

Hi @leighno5

Yes, I understand.

Those “HOWTO” plugin topics were written by very talented developers with over a decade of Rails and Ruby programming experience (maybe even more). In fact, some of those guys are among the top Ruby and JS developers in the world.

I am sure, 100%, that those guys all learned Rails and Ruby long before they wrote their first Discourse plugin.

It’s actually not that hard to learn Rails; but you need to actually do the “hands on ritual” of building a few Rails apps from the ground up. Not just following along a YT tutorial (which is good); but actually building a working application where you must deal with setting up the database, generating your own models, generating your own controllers, writing model validations, writing initializers, helper modules, migrating (modifying) the DB, working with embedded Ruby and more.

It’s actually very fun and it is the “shortest path” to learning to actually feel comfortable to write a plugin (regarding the Rails side of things, I don’t really do Ember personally, but love the Rails side of the house)!

Also, you must learn the Rails console! The Rails console is really fun and I use it everyday with pleasure!

Today, my favorite client and I were doing AnyDesk and he said “can we have this new view of Inventory?”. He wanted to watch me do it all. After we got it up and running, at the end of the call he said "you did something in Rails in 30 minutes that would have taken many weeks a few decades ago!!). Truly awesome, @leighno5. Go for it!!

After you learn Rails, you will say “WOW” writing Discourse plugins are pretty easy, at least for the Rails and Ruby part!!

HTH

3 Likes

Look at existing plugin code. There are absolutely loads of them now so plenty of examples. Do not skip on reading prior art so you can leverage it.

Sounds like you just want to override one method which is simply a case of redefining it in your plugin.rb (though best practice is arguably to break it out into its own file per module, but not worth it for one method!)

5 Likes

At the risk of further showing my ignorance and taking advantage of the kindness you’ve already shown me, would you be willing to expand on this a bit? Or if you’re aware of another example where someone has overridden a module and can link that, I’d be grateful. I’m not at all opposed to “rtfm,” but my lack of understanding of how Discourse works is frustrating, despite all the time I’ve spent going over various tutorials and documents. And yes, I know such basic questions make it seem that I’ve put little effort into it, but I promise that’s not the case!

3 Likes

Here’s one where we’ve defined a method in the PostGuardian:

https://github.com/paviliondev/discourse-topic-previews/blob/f4332f9742a37827fd6364b2365c45524f1e7faf/lib/guardian_edits.rb#L10

You could do something similar for the UserGuardian using the existing method name which should override it.

Simple solution (to get it working if not robustly):

Literally just write out the original method code and make your edit. Downside: if Discourse change the code your plugin could break the instance.

Advanced solution (more robust solution):

For extra points you might be able to use super to inherit the original method but that’s not guaranteed to work for reasons I shan’t elaborate here on but is best practice because then updates to that method in discourse will always be taken into account without you having to edit your plugin.

Discourse is a platform. It takes time to learn it. Just take it step by step.

6 Likes

This is what I was worried about. Is the advanced method mentioned in your next paragraph covered in the how-to guide? Is there a good, simple example of it being used in an existing plugin that could be used (copied) in creating a new plugin?

I wouldn’t worry about the advanced method for now. Just get it working. You can research Ruby on Rails in general elsewhere in the online guides. A bit out of scope of Discourse specifically and this forum. Just be aware.

1 Like

Ok cheers! Let’s say I get round to writing a short plugin. If I’ve understood it correctly, I should then check the core code on each upgrade and rewrite the plugin if the relevant core code has changed. Is that it? Thanks.

1 Like

That’s correct. Just check it occasionally and especially if it starts to malfunction. Guardian code is a little more critical as it’s coding site authorization so keep an eye on it.

Another solution is to add test cases (aka specs) and set up an automated job to run them … but that’s getting involved!

2 Likes

Another extra credit task is to set up travis-ci.org to run tests of your plugin so that you’ll get a heads up if it breaks.

5 Likes

Clearly not a task to be taken lightly. I’d like to change the login/create new account modal(s), and to get rid of the Twitter image. Maybe the latter would be a better first project. Thank you.

If anyone comes across this and was as lost as I was, I’m providing my code below, as I got it working as intended. I did not modify any files. I simply created a new file plugin.rb and placed within it the below:

# about: Allows users in Anonymous Mode to change their usernames
# version: 0.1


require_dependency 'guardian'
require_dependency 'guardian/user_guardian'

class ::Guardian
end

module ::UserGuardian
  def can_edit_username?(user)
    return false if SiteSetting.sso_overrides_username?
    return true if is_staff?
    return false if SiteSetting.username_change_period <= 0
    return true if is_anonymous?
    is_me?(user) && ((user.post_count + user.topic_count) == 0 || user.created_at > SiteSetting.username_change_period.days.ago)
  end


  def can_edit_name?(user)
    return false unless SiteSetting.enable_names?
    return false if SiteSetting.sso_overrides_name?
    return true if is_staff?
    return true if is_anonymous?
    can_edit?(user)
  end
end

This overrides the user_guardian.rb class in the Discourse core to allow users to change their username and name of their anonymous user accounts.

If you compare what I have here with what’s in user_guardian.rb in Discourse core, you can see there’s a bunch of other stuff I didn’t need to do anything with, so left it alone. All I needed was to edit those two methods can_edit_username and can_edit_name by changing some return values from false to true and I was able to get what I needed done.

Certainly there are enhancements that could be made, and there is likely best practice that I was able to glean by reading the linked posts here and elsewhere, but if you’re brand new to Ruby like I am and just want to get something very simple tweaked in core, this is a working minimalist example on what you need to do.

Many thanks to the people in this thread for their patience with me and their encouragement and assistance! In particular @merefield who has actually helped me with another post in the past.

10 Likes

LOL

You have come a long way from your initial idea to fork all of Discourse and abandon the main branch for these kinds of simple tasks.

Well done :slight_smile:

4 Likes

Did you go down the whole Install Discourse on Ubuntu or Debian for Development route or did you just work on your production site and hope for the best?